hello2dj

if you can't explain it simply, you don't understand it well enough

egg入门

egg是基于koa的企业级web框架

egg的使用感觉

  1. 约定大于配置
  2. koa中间件基本无痛使用
  3. 可以总结业务通用基础服务(生产基础框架),而不用费心费力再重新写插件啥的

    app -> plugins -> framework (统统一套约定)

照着官网总结了一部分详细的还得去官网查询

官方文档进行搭建

  1. npm i egg-init -g
  2. egg-init egg-example –type=simple

    关于 type: egg 本身的集成包括了很多的东西,从一个基本的webserver走起,沉淀出plugin, 最后是framework 于是type就很明显了webserver== simple…

整体结构 MVC

文件夹约定

所有相应的文件必须放在指定目录中

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
egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app
| ├── router.js
│ ├── controller
│ | └── home.js
│ ├── service (可选)
│ | └── user.js
│ ├── middleware (可选)
│ | └── response_time.js
│ ├── schedule (可选)
│ | └── my_task.js
│ ├── public (可选)
│ | └── reset.css
│ ├── view (可选)
│ | └── home.tpl
│ └── extend (可选)
│ ├── helper.js (可选)
│ ├── request.js (可选)
│ ├── response.js (可选)
│ ├── context.js (可选)
│ ├── application.js (可选)
│ └── agent.js (可选)
├── config
| ├── plugin.js
| ├── config.default.js
│ ├── config.prod.js
| ├── config.test.js (可选)
| ├── config.local.js (可选)
| └── config.unittest.js (可选)
└── test
├── middleware
| └── response_time.test.js
└── controller
└── home.test.js

内置对象

  1. Application (ctx.app, this.app)
  2. Response/Request (ctx.request, ctx.response)
  3. Context (this.ctx)
  4. Service (this.service)`
  5. Helper (this.ctx.helper)
  6. Logger (ctx.logger, this.logger(应用打印log)) 还有coreLogger(框架打印log)
  7. Config (this.config, app.config)
  8. Subscription 订阅发布模型的规范基类

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// app/controller/post.js
const Controller = require('egg').Controller;
class PostController extends Controller {
async create() {
const { ctx, service } = this;
const createRule = {
title: { type: 'string' },
content: { type: 'string' },
};
// 校验参数
ctx.validate(createRule);
// 组装参数
const author = ctx.session.userId;
const req = Object.assign(ctx.request.body, { author });
// 调用 Service 进行业务处理
const res = await service.post.create(req);
// 设置响应内容和响应状态码
ctx.body = { id: res.id };
ctx.status = 201;
}
}
module.exports = PostController;
  • this.ctx: 当前请求的上下文 Context 对象的实例,通过它我们可以拿到框架封装好的处理当前请求的各种便捷属性和方法。

  • this.app: 当前应用 Application 对象的实例,通过它我们可以拿到框架提供的全局对象和方法。

  • this.service:应用定义的 Service,通过它我们可以访问到抽象出的业务层,等价于 this.ctx.service 。

  • this.config:应用运行时的配置项。

  • this.logger:logger 对象,上面有四个方法(debug,info,warn,error),分别代表打印四个不同级别的日志,使用方法和效果与 context logger 中介绍的一样,但是通过这个 logger 对象记录的日志,在日志前面会加上打印该日志的文件路径,以便快速定位日志打印位置。

service

1
2
3
4
5
6
7
8
9
10
11
// app/service/user.js
const Service = require('egg').Service;

class UserService extends Service {
async find(uid) {
const user = await this.ctx.db.query('select * from user where uid = ?', uid);
return user;
}
}

module.exports = UserService;

this对象和controller中能拿到的实例是相同的, service的使用是依据文件名来查找的,this.service.filename.method

service 在使用构造函数时需要传递ctx参数

1
2
3
4
5
6
7
8
class User extends app.Service {
// 默认不需要提供构造函数。
// constructor(ctx) {
// super(ctx); 如果需要在构造函数做一些处理,一定要有这句话,才能保证后面 `this.ctx`的使用。
// // 就可以直接通过 this.ctx 获取 ctx 了
// // 还可以直接通过 this.app 获取 app 了
// }
}

router

http://eggjs.org/zh-cn/basics/router.html

基本使用

1
2
3
4
5
// app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('/user/:id', controller.user.info);
};

middlerware

基本示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const isJSON = require('koa-is-json');
const zlib = require('zlib');

async function gzip(ctx, next) {
await next();

// 后续中间件执行完成后将响应体转换成 gzip
let body = ctx.body;
if (!body) return;
if (isJSON(body)) body = JSON.stringify(body);

// 设置 gzip body,修正响应头
const stream = zlib.createGzip();
stream.end(body);
ctx.body = stream;
ctx.set('Content-Encoding', 'gzip');
}

使用

  1. 全局路由使用
    module.exports = {
    // 配置需要的中间件,数组顺序即为中间件的加载顺序
    middleware: [ ‘gzip’ ],

    // 配置 gzip 中间件的配置
    gzip: {
    threshold: 1024, // 小于 1k 的响应体不压缩
    },
    };

  2. 某个路由使用

    1
    2
    3
    4
    module.exports = app => {
    const gzip = app.middleware.gzip({ threshold: 1024 });
    app.router.get('/needgzip', gzip, app.controller.handler);
    };
  3. 框架默认中间件使用
    例如,想把某个自定义中间件放到所有中间件之前执行

    1
    2
    3
    4
    5
    // app.js
    module.exports = app => {
    // 在中间件最前面统计请求时间
    app.config.coreMiddleware.unshift('report');
    };

上面的顺序可随意互换

  1. koa中间件基本可无痛使用

通用配置(无论是框架还是自定义中间件都可以)

  1. enable:控制中间件是否开启。
  2. match:设置只有符合某些规则的请求才会经过这个中间件。
  3. ignore:设置符合某些规则的请求不经过这个中间件。

插件(就是个mini的egg应用,只是没有路由和controller罢了)

http://eggjs.org/zh-cn/advanced/plugin.html

可以把我们的通用逻辑沉淀为插件

插件配置
示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
exports.mongoose = {
enable: true,
package: 'egg-mongoose',
};

exports.session = false;

exports.cors = {
enable: true,
package: 'egg-cors',
};

exports.jwt = {
enable: true,
package: 'egg-jwt'
};

exports.routerPlus = {
enable: true,
package: 'egg-router-plus',
};

定时任务

http://eggjs.org/zh-cn/basics/schedule.html

框架扩展

  1. context
  2. application
  3. response
  4. request
  5. helper
    扩展就是说可以通过context等实例直接访问到
    例如,我们要增加一个 ctx.foo() 方法:
    1
    2
    3
    4
    5
    6
    // app/extend/context.js
    module.exports = {
    foo(param) {
    // this 就是 ctx 对象,在其中可以调用 ctx 上的其他方法,或访问属性
    },
    };

特性根据环境进行框架扩展
比如unittest环境app实例有mockXX方法

1
2
3
4
5
// app/extend/application.unittest.js
module.exports = {
mockXX(k, v) {
}
};

启动自定义(可以执行一些异步初始化的工作)

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = app => {
app.beforeStart(async () => {
// 应用会等待这个函数执行完成才启动
app.cities = await app.curl('http://example.com/city.json', {
method: 'GET',
dataType: 'json',
});

// 也可以通过以下方式来调用 Service
// const ctx = app.createAnonymousContext();
// app.cities = await ctx.service.cities.load();
});
};

配置

基本默认

1
2
3
4
5
6
config
|- config.default.js
|- config.test.js
|- config.prod.js
|- config.unittest.js
`- config.local.js

配置加载顺序
-> 插件 config.default.js
-> 框架 config.default.js
-> 应用 config.default.js
-> 插件 config.prod.js
-> 框架 config.prod.js
-> 应用 config.prod.js

配置示例

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
module.exports = appInfo => {
appInfo;
const config = exports = {};

// add your config here
config.middleware = ['errorhandler', 'auth'];
config.auth = {
ignore: []
};

// mongoose
config.mongoose = {
url: 'mongodb://localhost:27017/trace-to-source',
options: {},
};

// multipart set
config.multipart = {
fileSize: '30mb',
whitelist: [
'.xlsx',
'.jpg',
'.png',
'.zip',
],
};

config.security = { // 关闭csrf安全防范(关闭后post delete put请求可以不携带csrf-token)
domainWhiteList: ['http://47.104.6.246:8090', 'http://localhost:8000', 'https://localhost:8000'],
csrf: {
enable: false,
},
};

// cors
config.cors = {
credentials: true,
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
};

// logger
config.logger = {
buffer: true, // 文件写入时缓存 https://github.com/eggjs/egg-logger/blob/master/lib/egg/loggers.js#L44
maxDays: 0,
};

config.jwt = {
secret: 'lksjdhglaiejf'
};

return config;
};

运行环境

  1. 通过 config/env 文件指定,该文件的内容就是运行环境,如 prod。一般通过构建工具来生成这个文件。
  2. 通过 EGG_SERVER_ENV 环境变量指定。
    一般使用 EGG_SERVER_ENV指定

自定义环境

比如,要为开发流程增加集成测试环境 SIT。将 EGG_SERVER_ENV 设置成 sit(并建议设置 NODE_ENV = production),启动时会加载 config/config.sit.js,运行环境变量 app.config.env 会被设置成 sit

应用内获取运行环境

框架提供了变量 app.config.env 来表示应用当前的运行环境。

测试

ctx测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const assert = require('assert');
const mock = require('egg-mock');

describe('test/controller/home.test.js', () => {
let app;
before(() => {
// 创建当前应用的 app 实例
app = mock.app();
// 等待 app 启动成功,才能执行测试用例
return app.ready();
});
it('should mock ctx.user', () => {
const ctx = app.mockContext({
user: {
name: 'fengmk2',
},
});
assert(ctx.user);
assert(ctx.user.name === 'fengmk2');
});
});

http请求测试(controller 测试)

1
2
3
4
5
6
// 使用 async
it('should redirect', async () => {
await app.httpRequest()
.get('/')
.expect(302);
});

service 测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
describe('get()', () => {
it('should get exists user', async () => {
// 创建 ctx
const ctx = app.mockContext();
// 通过 ctx 访问到 service.user
const user = await ctx.service.user.get('fengmk2');
assert(user);
assert(user.name === 'fengmk2');
});

it('should get null when user not exists', async () => {
const ctx = app.mockContext();
const user = await ctx.service.user.get('fengmk1');
assert(!user);
});
});

更多测试

多进程架构

1
2
3
4
5
6
7
8
9
10
11
                +--------+          +-------+
| Master |<-------->| Agent |
+--------+ +-------+
^ ^ ^
/ | \
/ | \
/ | \
v v v
+----------+ +----------+ +----------+
| Worker 1 | | Worker 2 | | Worker 3 |
+----------+ +----------+ +----------+

启动顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+---------+           +---------+          +---------+
| Master | | Agent | | Worker |
+---------+ +----+----+ +----+----+
| fork agent | |
+-------------------->| |
| agent ready | |
|<--------------------+ |
| | fork worker |
+----------------------------------------->|
| worker ready | |
|<-----------------------------------------+
| Egg ready | |
+-------------------->| |
| Egg ready | |
+----------------------------------------->|

消息发送

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
广播消息: agent => all workers
+--------+ +-------+
| Master |<---------| Agent |
+--------+ +-------+
/ | \
/ | \
/ | \
/ | \
v v v
+----------+ +----------+ +----------+
| Worker 1 | | Worker 2 | | Worker 3 |
+----------+ +----------+ +----------+

指定接收方: one worker => another worker
+--------+ +-------+
| Master |----------| Agent |
+--------+ +-------+
^ |
send to / |
worker 2 / |
/ |
/ v
+----------+ +----------+ +----------+
| Worker 1 | | Worker 2 | | Worker 3 |
+----------+ +----------+ +----------+

egg-logger 使用

  • 自定义transport时一定要传递level参数

    1
    2
    3
    4
    5
    6
    7
    8
    class RemoteTransport extends Transport {
    // level 必传
    constructor({ level, app }) {
    super({ level });
    this._level = level;
    this._app = app;
    }
    }
  • 自定logger在配置文件中必须传file参数, 必须在customLogger下面配置

    1
    2
    3
    4
    5
    config.customLogger = {
    remoteLogger: {
    file: path.join(appInfo.root, 'logs/remote.log'),
    },
    };
  • 所有logger的错误error方法都被errorLogger的log方法劫持了,所以当我们使用自定义logger的error方法打印时其实是被errorLogger输出了,
    所以若是自己配置的自定义logger中有其他处理使用error时是不会被触发的

    原因见下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * Add a logger
    * @param {String} name - logger name
    * @param {Logger} logger - Logger instance
    */
    set(name, logger) {
    if (this.has(name)) {
    return;
    }

    // redirect ERROR log to errorLogger, except errorLogger itself
    if (name !== 'errorLogger') {
    logger.redirect('error', this.errorLogger);
    }
    this[name] = logger;
    super.set(name, logger);
    }

hack

  1. egg-mongoose 里面挂载在app上的mongoose是个conn对象,base才是mongoose实例
  2. egg 里面的hack, app下面有个serviceClasses属性里面是所有的service的构造函数,所以在拿不到service实例时,
    可以自己new一个,切记要传入ctx对象,可以通过app.createAnonymousContext创建一个匿名ctx

docker相关的一些使用总结

随时更新

解决docker sudo问题

检查是否已有 docker 用户组

1
cat /etc/group | grep docker

将现有用户加入 docker 组

1
sudo gpasswd -a ${USER} docker

重启 docker 服务

1
sudo service docker restart

退出当前用户,重新登录

docker 的远程镜像名字得和本地一致且以 url 为前缀

比如 docker push docker.sensoro.com/library/ai-server,那么推送的地址是 docker.sensoro.com/library/ 我们的镜像想要叫 ai-server。 但是本地在 build 时,要用 docker build ./ -t docker.sensoro.com/library/ai-server tag 必须是这个。

docker compose

  • networks 负责创建一个网络
  • sysctls 负责内核参数

  • ulimits

swarm

  1. docker swarm init
  2. 根据上条命令输出执行即可

docker daemon api 链接

docker 在配置为 host 网络模式时,是不需要映射端口的,因为 docker 容器的网络(ip)就是 host(宿主)的网络 IP,并没有进行隔离

网络模式

  1. host 模式

    众所周知,Docker 使用了 Linux 的 Namespaces 技术来进行资源隔离,如 PID Namespace 隔离进程,Mount Namespace 隔离文件系统,Network Namespace 隔离网络等。一个 Network Namespace 提供了一份独立的网络环境,包括网卡、路由、Iptable 规则等都与其他的 Network Namespace 隔离。一个 Docker 容器一般会分配一个独立的 Network Namespace。但如果启动容器的时候使用 host 模式,那么这个容器将不会获得一个独立的 Network Namespace,而是和宿主机共用一个 Network Namespace。容器将不会虚拟出自己的网卡,配置自己的 IP 等,而是使用宿主机的 IP 和端口。例如,我们在 10.10.101.105/24 的机器上用 host 模式启动一个含有 web 应用的 Docker 容器,监听 tcp80 端口。当我们在容器中执行任何类似 ifconfig 命令查看网络环境时,看到的都是宿主机上的信息。而外界访问容器中的应用,则直接使用 10.10.101.105:80 即可,不用任何 NAT 转换,就如直接跑在宿主机中一样。但是,容器的其他方面,如文件系统、进程列表等还是和宿主机隔离的。

  2. container 模式

    在理解了 host 模式后,这个模式也就好理解了。这个模式指定新创建的容器和已经存在的一个容器共享一个 Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过 lo 网卡设备通信。

  3. none 模式

    这个模式和前两个不同。在这种模式下,Docker 容器拥有自己的 Network Namespace,但是,并不为 Docker 容器进行任何网络配置。也就是说,这个 Docker 容器没有网卡、IP、路由等信息。需要我们自己为 Docker 容器添加网卡、配置 IP 等。

  4. bridge 模式

    bridge 模式是 Docker 默认的网络设置,此模式会为每一个容器分配 Network Namespace、设置 IP 等,并将一个主机上的 Docker 容器连接到一个虚拟网桥上。下面着重介绍一下此模式。

node图像库

image-demo

  • 需要一个能在图片上写汉字色图片处理库

图片库比较

1.node-images

  • 国产图片处理库node-images,已经停止维护,缺乏某些功能,比如图片上加文字,并且在nodejs 8.0版本下无法使用

2.Jimp

  • Jimp无需第三方依赖,安装简单,基本无坑,Github star4200多,npm周下载量7万多次,算是比较优秀的第三方处理库。支持常见的图片处理操作,也可以加文字,但是如果要在图片上加中文文字的话,有点麻烦,需要自制.fnt位图字体文件,然后导入。但CJK字库文字太多,制作出来文件很大。我就是因为这个,放弃了使用jimp。

3.node-lwip

  • 和jimp类似,无第三方依赖。没有提供图片上写文字的方法。

4.sharp

  • 目前nodejs中最快的图片处理库,和其他图片处理库相比,遥遥领先。无需第三方依赖,性能超好,就是安装比较麻烦,但最后还是安装成功了!一次性处理200张图片,sharp图片处理库的速度明显最快。按照官方说明,至少是5到10倍于ImageMagick and GraphicsMagick 。
    可惜,翻遍文档api也没有找到图片写文字的方法,就是说,没有提供图片直接写文字的方法。
  • 关于sharp在windows 10 64位的安装,直接npm install sharp,基本都不会成功的。
  • Sharp的安装,需要三个前提条件:
  • Node v4.5.0+
  • C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+
  • node-gyp and its dependencies (includes Python)
  • 对于windows10 64位来说,可以运行以下两条命令,一劳永逸解决sharp的安装问题:
  • 先运行:
  • npm install –global –production windows-build-tools
  • 然后:
  • npm config set msvs_version 2015 –global
  • 就可以把所有的环境配置搞定。最后关掉所有cmd或者shell窗口,然后再用
  • npm install sharp安装就OK了。
  • 速度超快,api好用,基本等于完美,可惜刚好我缺乏我需要的一个功能。

5.基于GraphicsMagick和ImageMagick的gm

  • 如果不需要在图片上加中文文字,只需要安装GraphicsMagick就可以了。如果需要在图片上加【中文文字】,要同时安装GraphicsMagick 和 ImageMagick。然后使用gm subclass子类话的方法来调用。
  • 注意:中文需要指定中文字体的.ttf文件,并且字体文件名不能是中文,如”msyh.ttf”,OK;“微软雅黑.ttf”,BAD!
  • gm提供了超级强悍的api,基本上你需要对图片做的任何处理都能实现!并且它是基于命令行的,可以直接在命令行中调用。
  • GraphicsMagick和ImageMagick在windows10 64位下的安装都比较傻瓜式,直接下载对应的exe文件,不分32位和64位,setup 一路next就行。
  • 总结,gm的速度仅仅比sharp慢,比其他的几个图片库都要快的多,并且提供的api很丰富,链式调用写法很爽,安装配置也相对简单的多,支持在图片上直接添加中文文字。其他的几个图片库,对于”在图片上添加文字“这个功能,有些不提供,有些提供了但很难用。

提供一段gm的示例代码(demo文件夹中)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var gm = require('gm');
var fs = require('fs');
var _name = "China中文";
gm('./0.jpg')
.font('./st_black.ttf',30)
.drawText(20, 30,"你")
.drawText(50, 50, "好")
.drawText(70, 70,"色")
.drawText(110, 110, "彩")
//.resize(240, 240)
.toBuffer("jpg",(err, buffer)=> {
fs.writeFileSync("./4.jpg", buffer);
if (!err){
console.log('done');
}else{
console.log(err.message || "出错了!");
}
});

red-hat 安装gm

ImageMagick 安装

  • 安装 yum install ImageMagick
  • 查看是否安装成功 rpm -qa | grep ImageMagick

gm 安装

  • npm install gm –save

测试代码

1
2
3
4
5
6
7
var gm = require("gm");
var imageMagick = gm.subClass({ imageMagick : true });
var fs = require('fs');
imageMagick(300, 300, "pink").toBuffer("jpg", (err, buffer)=>{
fs.writeFileSync("./fill.jpg", buffer);
console.log("image create success!")
})
  • 会生成一个300*300,背景色为粉色的图片

#环境配置

  • 代码运行环境

    1.node

    • 开发时用的 8.9.0, node直接更新到最新的版本即可,没有太多限制

    2.gm的依赖环境

    mac下环境配置

      1. 安装Xcode软件 很重要 在mac的应用中心就可以安装
      1. 安装imagemagick brew install imagemagick
      1. 安装graphicsmagick brew install graphicsmagick
      1. 以上3步之后 npm install gm 基本就没什么问题了

    centos下环境配置

      1. 安装 imagemagick graphicsmagick的依赖
    • yum install -y gcc libpng libjpeg libpng-devel libjpeg-devel ghostscript libtiff libtiff-devel freetype freetype-devel
      1. 安装imagemagick
    • yum install ImageMagick
      1. 安装graphicsmagick
    • wget ftp://ftp.graphicsmagick.org/pub/GraphicsMagick/1.3/GraphicsMagick-1.3.25.tar.gz
    • tar -zxvf GraphicsMagick-1.3.25.tar.gz
    • cd GraphicsMagick-1.3.25
    • ./configure
    • make
    • make install
    • 4.以上步骤之后 gm基本就能使用

pki 体系

PKI

CA 自建 ca

https://www.jianshu.com/p/79c284e826fa
https://www.barretlee.com/blog/2016/04/24/detail-about-ca-and-certs/

  1. 生成秘钥(可选,因为后面的步骤也可以自动生成秘钥)
    openssl genrsa

  2. 生成 CA 证书请求
    openssl req [-key 使用已生成的秘钥][-keyout 未声明秘钥需自动生成]

  3. 自签发 ca 根证书
    openssl ca -selfsign(需使用上述的请求证书)

  4. 2,3 两步可以合在一起
    openssl req -new -x509(关键是-x509 生成 509 的根证书)

签发证书也是上述的步骤只是签发证书时不需要-selfsign 选项

证书发放

向 CA 请求证书时需要先生成一个 CSR(certificate signing request), 证书签发的请求。包括一些申请者的信息,申请者的公钥,还有 Distinguished Name(专有名称类似于发放者的唯一标示)用来标识证书时发给谁的。

pem 代表 Privacy-enhanced Electronic Mail 一种文件格式 base64 编码显示

CA DN distinguished name

  • .cer/.crt 是用于存放证书,它是 2 进制形式存放的,不含私钥。
  • X.509 DER 编码(ASCII)的后缀是: .DER .CER .CRT
  • X.509 PAM 编码(Base64)的后缀是: .PEM .CER .CRT
  • .pem 跟 crt/cer 的区别是它以 Ascii 来表示

CSR 文件 包含了公钥和标识名称(Distinguished Name)

intermediate ca

证书链

root ca -> intermediate ca -> end user

数字证书

tsl 认证

  • 单向认证时,客户端需要有 server 的根证书来验证 server 的证书是否 ok
  • 双向时,服务端也得有客户端的根证书来验证 client 的证书是否 ok

私钥使用来解密公钥加密的数据,证书用来验证身份的,而证书又包含公钥,因此证书和私钥永远是一对儿。

流程就是把证书发给对方,对方验证证书是否 ok,然后取出证书中的公钥,用公钥加密数据然后发回来,此时再用私钥解密即可获得数据。

go map实战

go maps 实践

  • 简介
    在cs中hash table是一种经常使用的数据结构。许多hash table的实现都拥有很多的属性。但总的来说,他们都会提供快速查询,添加以及删除等功能。go 提供了一个内置的实现了hash table的map 类型。

  • 声明和初始化
    go map类型的签名如下

    1
    map[KeyType]ValueType

KeyType要求是能够comparable的类型, 而ValueType则可以是任意的类型,甚至是另一个map

如下一个key为string,value是int的map

1
var m map[string]int

map类型也是引用类型,就像指针或者切片一样,因此上面的声明的m是nil;我们还没有实例化map。一个值是nil的map再读取的时候就像是个空map你啥都读不到

1
2
value, ok := m["c"]
// value is 0, ok is false

但是写入nil map是会报错的。千万记得不要这么做要记得初始化map,请使用内置的make function(make 专门用来分配内存,初始化map, slice, channel)

1
m = make(map[string]int)

make会分配内存并且初始化然后返回一个map值,注意make不生成指针new才是返回指针但是new只分配内存,而不初始化。make的实现底层是基于的c的实现,本文只关注怎么使用,就不分析他的实现了。

  • 使用maps
    go 提供了便捷的语法来操作map例如赋值
    1
    m["route"] = 66

加下来取值

1
i := m["route"]

若是我们取得值不存在那么我们取到的会是值相应类型的默认值(zero value)。在我们的例子中我们读到的就是0:

1
2
j := m["root"]
// j == 0

内置的len函数可以得到map中的元素个数

1
2
n := len(m)
// n == 1

内置的delete函数是用来删除map中的元素

1
delete(m, "route")

delete没有返回值,并且若是删除的key不存在则啥都不处理

还有一种读取的语法如下

1
i, ok := m["route"]

这个语法是:i取得是m中route对应的数据,若是不存在route对应的数据则i会是对应类型的零值,而ok代表的是route在m是否存在,false即是不存在也就是说没有读到i值。

当我们只是为了验证是否存在相应的key时可以使用下划线来忽略key对应的数据

1
_, ok := m["route"]

为了遍历map,我们可以使用range关键字

1
2
3
for key, value := range m {
fmt.Println("Key:", key, "Value:", value)
}

若是不使用make,我们也可以使用map的字面量来初始化一个map

1
2
3
4
commits := map[string]int {
"rsc": 3711,
"r", 2138,
}

我们还可以初始化一个空map,和使用make是一样的

1
var m = map[string]int{}

  • 对默认值的利用
    当我们读取的key不存在时返回默认值有的是很方便的。

比如,一个值为bool类型的map就可以看做是一个set类型的数据结构(要知道,布尔类型的默认值是false)。这个例子遍历一个linked list of nodes, 并且打印他们的值。他使用值类型是Node 指针的map来检测list是否有环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Node struct {
Next *Node
Value interface{}
}
var first *Node

visited := make(map[*Node]bool)
for n := first; n != nil; n = n.Next {
if visited[n] {
fmt.Println("cycle detected")
break
}
visited[n] = true
fmt.Println(n.Value)
}

若Node n已经被访问过了,则visited[n]的值是true, 若值是false则说明Node n没有被访问过。我们不需要再用其他数据来判断node在map中的存在性,map默认值已经帮我们处理了。

另一个有用的例子是slices的map, 我们知道当我们想一个nil slice append数据的时候是会分配新的内存的。因此当我们向slice的map中append 数据的时候,是不需要检查key是否存在的。可以看看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
type Person struct {
Name string
Likes []string
}

var people []*Person

likes := make(map[string][]*Person)
for _, p := range people {
for _, l := range p.Likes {
likes[l] = append(likes[l], p)
}
}

我们开一个打印出喜欢cheese的人:

1
2
3
for _, p := range likes["cheese"] {
fmt.Println(p.Name, "likes cheese.")
}

打印出有多少人喜欢bacon

1
fmt.Println(len(likes["bacon"]), " people like bacon.")

range 和 len都把nil slice当做长度是0的slice, 因此我们最后两个数据是不会出错的。

  • key的类型
    前面提到过了,就是map的keys必须是可比较的。语言规范已经详细定义了可比较。 总的来说可比较的类型就是boolean, numeric, string, pointer, channel 以及接口类型,还有只包含上述类型的结构体和数据。不在上述范围的类型有map, slice 和 functions; 这些类型是不能用==,也不能当做map的keys的。

很明显strings, ints, 以及一些其他的类型可以做key,但是结构体就有点而出乎意料了。
让我们看这个

1
hits := make(map[string]map[string]int)

这是一个页面访问的次数的map,key对应二级url

1
n := hits["/doc/"]["au"]

但我们这么访问是错的,因为map是需要实例化的,我们可以这么读,但是当我们添加的时就会有问题,我们需要去初始化内部的map。如下

1
2
3
4
5
6
7
8
9
func add(m map[string]map[string]int, path, country string) {
mm, ok := m[path]
if !ok {
mm = make(map[string]int)
m[path] = mm
}
mm[country]++
}
add(hits, "/doc/", "au")

但是我们可以采用另一种设计如下

1
2
3
4
5
type Key struct {
Path, Country string
}

hits := make(map[Key]int)

此时我们可以一步添加

1
hits[Key{"/", "vn"}]++

另外读取也是非常方便的

1
n := hits[Key{"/ref/spec/", "ch"}]

  • 并发
    Maps不是并发安全的:当我们同时读写时map的行为是未定义的。通常我们可以使用sync.RWMutex来保护map

看个例子

1
2
3
4
var counter = struct {
sync.RWMutex
m map[string]int
}{m: make(map[string]int)}

读取的时候就可以使用读锁

1
2
3
4
couter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)

写时用写锁

1
2
3
couter.Lock()
counter.m["some_key"]++
counter.Unlock()

sync.Mutex第一次被使用后,千万不可以复制,要传指针。因为sync.Mutex是结构体而非指针数据,接下来回来一篇文章分析一下的。

  • 迭代顺序
    map的迭代顺序是不包证的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import "sort"

    var m map[int]string
    var keys []int
    for k := range m {
    keys = append(keys, k)
    }

    sort.Ints(keys)
    for _, k := range keys {
    fmt.Println("Key:", k, "value:", m[k])
    }
  • 注意点
    map element是不可以addressable的意味着, 其实是可以预料的,因为map会扩容,那么扩容后map元素是否还在原地址就不一定了,所以&map[“x”]这个操作是不被允许的。

    1
    2
    3
    4
    5
    type data struct {
    name string
    }
    var a = map[string]data {"x": {"one"}}
    m["x"].name = "two" // error

错误的,除非他的类型是指针

1
2
3
4
5
6
7
8
9
type data struct {  
name string
}

func main() {
m := map[string]*data {"x":{"one"}}
m["x"].name = "two" //ok
fmt.Println(m["x"]) //prints: &{two}
}

但是要注意不要写入空的了指针,是会panic的

1
2
3
4
5
6
7
8
9
10
package main

type data struct {
name string
}

func main() {
m := map[string]*data {"x":{"one"}}
m["z"].name = "what?" //???
}

很显然,指针的默认值是nil,当然无法访问nil的name了。

但是slice element就可以addressable

1
2
3
4
5
6
7
8
9
type data struct {  
name string
}

func main() {
s := []data {{"one"}}
s[0].name = "two" //ok
fmt.Println(s) //prints: [{two}]
}

css的一些基础知识

随时会更新

生成 BFC

  • 根元素

  • float 属性不为 none

  • position 为 absolute 或 fixed

  • display 为 inline-block, table-cell, table-caption, flex, inline-flex

  • overflow 不为 visible

记忆规则: absolute, fixed, inline-block, overflow

BFC 布局规则

  • 非 BFC 盒子内的第一个元素的 margin-top 设置为正值是不会撑开父元素的,而是紧贴着把父元素的兄弟元素给撑开。
  • 内部的 Box 会在垂直方向,一个接一个地放置。

  • Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠

  • 每个元素的左外边缘(margin-left), 与包含块的左边(contain box left)相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。除非这个元素自己形成了一个新的 BFC。

  • BFC 的区域不会与 float box 重叠。(不会被兄弟float元素覆盖)

  • BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。

  • 计算 BFC 的高度时,浮动元素也参与计算

IFC 布局规则

  • 框会从包含块的顶部开始,一个接一个地水平摆放。
  • 摆放这些框的时候,它们在水平方向上的外边距、边框、内边距所占用的空间都会被考虑在内。在垂直方向上,这些框可能会以不同形式来对齐:它们可能会把底部或顶部对齐,也可能把其内部的文本基线对齐。能把在一行上的框都完全包含进去的一个矩形区域,被称为该行的行框。水平的 margin、padding、border 有效,垂直无效。不能指定宽高。
  • 行框的宽度是由包含块和存在的浮动来决定。行框的高度由行高计算这一章所描述的规则来决定。

IFC 结论

  • 一个 line box 总是足够高对于包含在它内的所有盒子。然后,它也许比包含在它内最高的盒子高。(比如,盒子对齐导致基线提高了)。
  • 当盒子 B 的高度比包含它的 line box 的高度低,在 line box 内的 B 的垂值对齐线通过’vertical align’属性决定。当几个行内级盒子在一个单独的 line box 内不能很好的水平放置,则他们被分配成了 2 个或者更多的垂直重叠的 line boxs.因此,一个段落是很多个 line boxs 的垂直叠加。Line boxs 被叠加没有垂直方向上的分离(特殊情况除外),并且他们也不重叠。
  • 通常,line box 的左边缘挨着它的包含块的左边缘,右边缘挨着它的包含块的右边缘。然而,浮动盒子也许会在包含块边缘和 line box 边缘之间。因此,尽管 line boxs 在同样的行内格式上下文中通常都有相同的宽度(就是他的包含块的宽度),但是水平方向上的空间因为浮动被减少了,它的宽度也会变得复杂。Line boxs 在同样的行内格式上下文中通常在高度上是多样的(比如,一行也许包含了一个最高的图片然后其他的也可以仅仅只包含文字)
  • 当在一行中行内级盒子的总宽度比包含他们的 line box 的宽度小,他们的在 line box 中的水平放置位置由’text align’属性决定。如果属性是’justify’,用户代理可能会拉伸空间和文字在 inline boxs 内。
  • 当一个行内盒子超过了 line box 的宽度,则它被分割成几个盒子并且这些盒子被分配成几个横穿过的 line boxs。如果一个行内盒子不能被分割。则行内盒子溢出 line box。
  • 当一个行内盒子被分割,分割发生则 margins,borders,和 padding 便没有了视觉效果。
  • 在同样的 line box 内的行内盒子也许会被分割成几个盒子因为双向的文字。Line boxs 在行内格式上下文中档需要包含行内级内容时被创造。Line boxs 包含没有文字,没有空格,没有带着 margins,padding 和 borders,以及没有其他在流中的内容(比如图片,行内盒子和行内表格),也不会以新起一行结尾。对于在他们内的任何盒子的位置都以他们决定并且必须将他们视作没有高度的 line boxs。

IFC 的 css

  • font-size
  • line-height
  • height
  • vertical-aligin

block ele 块级元素

a block-level box is also a block container box. A block container box either contains only block-level boxes or establishes an inline formatting context and thus contains only inline-level boxes. Not all block container boxes are block-level boxes: non-replaced inline blocks and non-replaced table cells are block containers but not block-level boxes. Block-level boxes that are also block containers are called block boxes.
解释一下 块级盒子也是一个块级包含块盒子, 他要么只包含块级盒子要么建立一个 IFC 并且只包含行级盒子。并不是所有的块级包含快盒子都是块级盒子,也有不可替换的 inline-block 或者其他的。

  • 解释 1 只包含块级元素或者是创建一个 IFC

    1. 只包含块级元素,就是说若是一个块级盒子里面包含了一个块级元素,那么它里面就只有块级元素!你在骗我吧,你看我是可以这么写的?
      1
      2
      3
      4
      5
      <div>
      <p>I love you</p>
      <span>真逗我不love你</span>
      那我来爱你吧
      <div>

    问:你看我这个 div 里不止有 p,还有 span 还有 text,你咋说?答:没错但是你看规范 9.2.1.1 Anonymous block boxes
    就是说像你这种情况他是会生成一个匿名块级盒子的,那么你的代码就是这样的了

    1
    2
    3
    4
    5
     <div>
    <p>I love you</p>
    <anonymous-block-boxes><span>真逗我不love你</span>
    那我来爱你吧</anonymous-block-boxes>
    <div>

    我觉得这也是合理的就像规范里说的,这种方式也易于实现,要不浏览器的实现不得费劲啊。这样规定就意味着,块级元素只包含块级元素或者是创建一个 IFC
    IFC 的栗子

    1
    <div>i love you</div>

    但事实上,这里不止创建 IFC 还有一个这个 Anonymous inline boxes,因为 html 里所有的东西都是被盒子包裹的文本也不例外,你没写盒子,并不代表就没有,其实是有匿名盒子生成的。

tricks

  • 两栏布局

  • 分栏高度自动相等

    • 使用 margin-bottom 负边距,padding-bottom 正边距, 原理是 padding-bottom 足够大让人误以为你的高度是在自动增加而实际是你增加的高度不能超过 padding-bottom 的值,否则就会看到白框了,margin-bottom 负边距是为了让后面的元素能够上移到 content 元素的后面,而不是被 padding 给撑开。
  • 水平垂直居中的方式

  • 层叠上下文 背景<布局<内容

层叠准则: 务必牢记的层叠准则

下面这两个是层叠领域的黄金准则。当元素发生层叠的时候,其覆盖关系遵循下面 2 个准则:

  • 谁大谁上:当具有明显的层叠水平标示的时候,如识别的 z-indx 值,在同一个层叠上下文领域,层叠水平值大的那一个覆盖小的那一个。通俗讲就是官大的压死官小的。
  • 后来居上:当元素的层叠水平一致、层叠顺序相同的时候,在 DOM 流中处于后面的元素会覆盖前面的元素。

层叠上下文的特性

层叠上下文元素有如下特性:

  1. 层叠上下文的层叠水平要比普通元素高(原因后面会说明);
  2. 层叠上下文可以阻断元素的混合模式(见此文第二部分说明);
  3. 层叠上下文可以嵌套,内部层叠上下文及其所有子元素均受制于外部的层叠上下文。
  4. 每个层叠上下文和兄弟元素独立,也就是当进行层叠变化或渲染的时候,只需要考虑后代元素。
  5. 每个层叠上下文是自成体系的,当元素发生层叠的时候,整个元素被认为是在父层叠上下文的层叠顺序中。

翻译成真实世界语言就是:

  1. 当官的比老百姓更有机会面见圣上;
  2. 领导下去考察,会被当地官员阻隔只看到繁荣看不到真实民情;
  3. 一个家里,爸爸可以当官,孩子也是可以同时当官的。但是,孩子这个官要受爸爸控制。
  4. 自己当官,兄弟不占光。有什么福利或者变故只会影响自己的孩子们。
  5. 每个当官的都有属于自己的小团体,当家眷管家发生摩擦磕碰的时候(包括和其他官员的家眷管家),都是要优先看当官的也就是主子的脸色。

层叠上下文的创建

  1. 根元素(很厉害的,本身是 BFC,还是具有层叠上下文)
  2. 定位元素对于包含有 position:relative/position:absolute 的定位元素,以及 FireFox/IE 浏览器(不包括 Chrome 等 webkit 内核浏览器)(目前,也就是 2016 年初是这样)下含有 position:fixed 声明的定位元素,当其 z-index 值不是 auto 的时候,会创建层叠上下文。典型的栗子,猜猜谁上谁下
1
2
3
4
5
6
<div style="position:relative; z-index:auto;">
<img src="mm1.jpg" style="position:absolute; z-index:2;"> <-- 横妹子 -->
</div>
<div style="position:relative; z-index:auto;">
<img src="mm2.jpg" style="position:relative; z-index:1;"> <-- 竖妹子 -->
</div>

还有这个

1
2
3
4
5
6
<div style="position:relative; z-index:auto;">
<img src="mm1.jpg" style="position:absolute; z-index:2;"> <-- 横妹子 -->
</div>
<div style="position:relative; z-index:auto;">
<img src="mm2.jpg" style="position:relative; z-index:1;"> <-- 竖妹子 -->
</div>
  1. CSS3 与新时代的层叠上下文
  2. z-index 值不为 auto 的 flex 项(父元素 display:flex|inline-flex).
    注意,这里的规则有些负责复杂。要满足两个条件才能形成层叠上下文:条件 1 是父级需要是 display:flex 或者 display:inline-flex 水平,条件 2 是子元素的 z-index 不是 auto,必须是数值。此时,这个子元素为层叠上下文元素,没错,注意了,是子元素,不是 flex 父级元素。
  3. 元素的 opacity 值不是 1.
  4. 元素的 transform 值不是 none.
  5. 元素 mix-blend-mode 值不是 normal.
  6. 元素的 filter 值不是 none.
  7. 元素的 isolation 值是 isolate.
  8. will-change 指定的属性值为上面任意一个。
  9. 元素的-webkit-overflow-scrolling 设为 touch.

再具体的请参见原文

同时设置 margin-left 和 margin-right 参见

更正: 第二种情况中若是没有设置宽度也是和第一种情况一样,设置负值会增加相应方向的宽度。

内容的包裹性,如何让你的盒子的宽度自适应内容呢?

  1. 行内元素肯定是可以的可是我们没办法控制行内元素的高度等属性
  2. float, inline-block,其实还有 position:absolute
    但是这真的可以么?也不是参见
    为什么还那么宽呢?因为我们里面放了两个 inline 元素,而第二个太长有放不下,于是挪到下一行了,但他占的位置依然在。so…

float 的一些规则

另一个
还有鑫空间的不贴了,一搜就有

宽度计算法则

position:absolute 的定位参考是包含块,若是没有定位为非 static 的父元素,则是依据的是 viewport 定位(是根元素的包含块)。

基本上来说,reflow 有如下的几个原因:

  • Initial。网页初始化的时候。
  • Incremental。一些 Javascript 在操作 DOM Tree 时。
  • Resize。其些元件的尺寸变了。
  • StyleChange。如果 CSS 的属性发生变化了。
  • Dirty。几个 Incremental 的 reflow 发生在同一个 frame 的子树上。我们来看一个示例:
1
2
3
4
5
6
7
8
9
10
11
var bstyle = document.body.style; // cache
bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; // 再一次的 reflow 和 repaint

bstyle.color = "blue"; // repaint
bstyle.backgroundColor = "#fad"; // repaint

bstyle.fontSize = "2em"; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

当然,我们的浏览器是聪明的,它不会像上面那样,你每改一次样式,它就 reflow 或 repaint 一次。一般来说,浏览器会把这样的操作积攒一批,然后做一次 reflow,这又叫异步 reflow 或增量异步 reflow。但是有些情况浏览器是不会这么做的,比如:resize 窗口,改变了页面默认的字体,等。对于这些操作,浏览器会马上进行 reflow。

但是有些时候,我们的脚本会阻止浏览器这么干,比如:如果我们请求下面的一些 DOM 值:

  • offsetTop(定位距离+margin-top), offsetLeft, offsetWidth(dom 对象的可见宽度包括滚动条等), offsetHeight
  • scrollTop(顶部已经滚动的距离)/Left/Width(元素完整的高度和宽度包括 overflow: hidden 的部分)/Height
  • clientTop(就是 border-top)/Left/Width(dom 内容的宽度)/Height
  • IE 中的 getComputedStyle(), 或 currentStyle

因为,如果我们的程序需要这些值,那么浏览器需要返回最新的值,而这样一样会 flush 出去一些样式的改变,从而造成频繁的 reflow/repaint。

各种 top,height(http://www.cnblogs.com/gagarinwjj/p/conflict_client_offset_scroll.html, https://github.com/pramper/Blog/issues/10)

  • offet 的 top, left 就是距离定位系统的父元素的距离(比如是相对父元素的定位那么算出来的就是距离父元素的距离)

event 对象中有

offsetX/Y, clientX/Y, pageX/Y, screenX/Y 等。

screenX:鼠标位置相对于用户屏幕水平偏移量,而 screenY 也就是垂直方向的,此时的参照点也就是原点是屏幕的左上角。

clientX:跟 screenX 相比就是将参照点改成了浏览器内容区域的左上角,该参照点会随之滚动条的移动而移动。

pageX:参照点也是浏览器内容区域的左上角,但它不会随着滚动条而变动

flex 布局(https://zhuanlan.zhihu.com/p/25303493)

  • 当设置了 display: flex 时, 子元素的 float、clear、vertical-align 都会失效
  • 并且当设置了 flex-basis 时 width 设置就不起作用了

getComputedStyle 可以获取伪类元素的样式

可以通过 parentNode 来获取 node 的 parent 元素

offsetParent (https://www.cnblogs.com/xiaohuochai/p/5828369.html) 是指元素的定位父元素(只有有 position 不为 static 才能成为 offsetParent 元素, 若是没有 position 定位的父级元素那么 offsetParent 就是 body, 而 fixed 元素的 offsetParent 是 null)

offsetParent 是用来计算 offsetTop 等值 的。他和 parentNode 是不同的,parentNode 父亲节点 HTML 结构层级关系中的上一级元素。因为所有的元素最后计算偏移都是和 body 元素的偏移。

.offsetTop/offsetLeft:当前元素距离父级参照物上/左边距偏移量, 我们要注意 offsetTop 可不是 scrollTop, 一个偏移距离,一个是滚动距离

->offset():等同于 jQuery 中的 offset 方法,实现获取页面中任意一个元素距离 body 的偏移(包含左偏移和上偏移),不管当前元素的父级参照物是谁。
->获取的结果是一个对象{left:距离 body 的左偏移量,top:距离 body 上偏移}
->在标准的 ie8 浏览器中,我们使用 offsetLeft/offsetTop 其实是把父级参照物的边框也计算在内了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function offset(curEle){
var totalLeft = null,totalTop = null,par = curEle.offsetParent;
//首先把自己本身的进行累加
totalLeft += curEle.offsetLeft;
totalTop += curEle.offsetTop;

//只要没有找到body,我们就把父级参照物的边框和偏移量累加
while(par){
if(navigator.userAgent.indexOf("MSIE 8.0") === -1){
//不是标准的ie8浏览器,才进行边框累加
//累加父级参照物边框
totalLeft += par.clientLeft;
totalTop += par.clientTop;
}
//累加父级参照物本身的偏移
totalLeft += par.offsetLeft;
totalTop += par.offsetTop;
par = par.offsetParent;
}
return {left:totalLeft,top:totalTop};

}
console.log(offset(box).top);

getBoundingClientRect

DOMRect 对象包含了一组用于描述边框的只读属性——left、top、right 和 bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口(viewPort)的左上角位置而言的。

getClientRects, 对于 块级元素 来说,这两个其实没有什么区别的,关键是对于 内联元素 这两个有明显的区别。简单的说就是 内联元素 不在一行的时候每行都会产生一个矩形范围,而 getBoundingClientRect 并不会。

getClientRects 返回的是数组,对于行内元素及其有用,尤其是当行内元素不在一行的时候

宽度计算

transform-origin 以及 transform: scale 的妙用

transform-orgin 默认是 50%, 50%

中间向两端延伸动画

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
div {

position: absolute;

width: 200px;

height: 60px;

}



div::before {

content: "";

position: absolute;

left: 0;

bottom: 0;

width: 200px;

height: 2px;

background: deeppink;

transition: transform .5s;

transform: scaleX(0);

}



div:hover::before {

transform: scaleX(1);

}

从左侧出来再右侧消失的动画

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
div {

position: absolute;

width: 200px;

height: 60px;

}



div::before {

content: "";

position: absolute;

left: 0;

bottom: 0;

width: 200px;

height: 2px;

background: deeppink;

transition: transform .5s;

transform: scaleX(0);

transform-origin: 100% 0;

}



div:hover::before {

transform: scaleX(1);

transform-origin: 0 0;

}

结合 transform-orgin 和 scale 可以做出很多初始位置与结束位置不同的动画
demo

使用 css 接收页面点击事件

  1. :target 是 CSS3 新增的一个伪类,可用于选取当前活动的目标元素。当然 URL 末尾带有锚名称 #,就可以指向文档内某个具体的元素。这个被链接的元素就是目标元素(target element)。它需要一个 id 去匹配文档中的 target 。
1
2
3
4
5
6
<ul class='nav'>
<li><a href="#content1">列表1</a></li>
<li><a href="#content2">列表2</a></li>
</ul>
<div id="content1">列表1内容:123456</div>
<div id="content2">列表2内容:abcdefgkijkl</div>

的那我们点击 a 标签时,如下的类选择器就会被触发

1
2
3
4
#content1:target,
#content2:target {
color: black;
}
  1. input[radio|checkbox] 点击可以触发 inpu:check 选择器,
1
2
3
4
5
6
7
8
9
10
11
12
<div class="container">
<input class="nav1" id="li1" type="radio" name="nav">
<input class="nav2" id="li2" type="radio" name="nav">
<ul class='nav'>
<li class='active'><label for="li1">列表1</label></li>
<li><label for="li2">列表2</label></li>
</ul>
<div class="content">
<div class="content1">列表1内容:123456</div>
<div class="content1">列表2内容:abcdefgkijkl</div>
</div>
</div>

多个元素的边界线问题,最后一个不要

消失的边界线问题,《css 设计指南》看到过,有一种巧妙的方法。
li+li{border-left: 1px solid #000;} // 单行 li

字体定义顺序是一门学问,通常而言,我们定义字体的时候,会定义多个字体或字体系列。举个栗子:

body {
font-family: tahoma, arial, ‘Hiragino Sans GB’, ‘\5b8b\4f53’, sans-serif;
}
别看短短 5 个字体名,其实其中门道很深。解释一下:

  1. 使用 tahoma 作为首选的西文字体,小字号下结构清晰端整、阅读辨识容易;
  2. 用户电脑未预装 tohoma,则选择 arial 作为替代的西文字体,覆盖 windows 和 MAC OS;
  3. Hiragino Sans GB 为冬青黑体,首选的中文字体,保证了 MAC 用户的观看体验;
  4. Windows 下没有预装冬青黑体,则使用 ‘\5b8b\4f53’ 宋体为替代的中文字体方案,小字号下有着不错的效果;
  5. 最后使用无衬线系列字体 sans-serif 结尾,保证旧版本操作系统用户能选中一款电脑预装的无衬线字体,向下兼容。嗯,其实上面的 font-family 就是淘宝首页 body 的字体定义,非常的规范,每一个字体的定义都有它的意义。综上,总结一下,我觉得字体 font-family 定义的原则大概遵循:

1、兼顾中西中文或者西文(英文)都要考虑到。

2、西文在前,中文在后由于大部分中文字体也是带有英文部分的,但是英文部分又不怎么好看,同理英文字体中大多不包含中文。

所以通常会先进行英文字体的声明,选择最优的英文字体,这样不会影响到中文字体的选择,中文字体声明则紧随其次。

3、兼顾多操作系统选择字体的时候要考虑多操作系统。例如 MAC OS 下的很多中文字体在 Windows 都没有预装,为了保证 MAC 用户的体验,在定义中文字体的时候,先定义 MAC 用户的中文字体,再定义 Windows 用户的中文字体;

4、兼顾旧操作系统,以字体族系列 serif 和 sans-serif 结尾当使用一些非常新的字体时,要考虑向下兼容,兼顾到一些极旧的操作系统,使用字体族系列 serif 和 sans-serif 结尾总归是不错的选择。

hover 的使用

hover

当 button 被点击时的状态变化 初始 -> hover(直到鼠标离开都是 hover) -> focus(点击后获取焦点,再点击其他或者 tab 键时会失去 focus) -> active(点击松开后就失去了), 因此 active 应当放到最后,否则会被作用时长比他长的样式覆盖,比如 hover 的样式

css loader 简单实现,border-radius, boder-top-color: 透明

1
2
3
4
5
6
7
8
9
10
11
12
.loader {

display: none;
width: 50px;
height: 50px;
border: 4px solid #fff;
border-top-color: transparent;
border-radius: 50%;
margin: 0 auto;
animation: spin 400ms linear infinite;

}

居中的方式

http://www.html-js.com/article/4613

图片预览的方式

基本原则就是造一个img标签

domReady

两栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.wrapper {
display: flex;
justify-content: space-between;
}
.left {
background: blue;
width: 200px;
height: 400px;
}
.right {
width: calc(100% - 200px);
height: 400px;
background: green;
}
1
2
3
4
5
6
7
8
9
10
11
12
.left {
background: blue;
float: left;
width: 200px;
height: 400px;
}
.right {
width: calc(100% - 200px);
height: 400px;
background: green;
display: inline-block;
}

CSS深入理解流体特性和BFC特性下多栏自适应布局

waterfall 布局

  1. 我先入为主以为waterfall 布局就是得横向一个一个布局的,但是若是所有的宽度一致的话,我们可以认为是按照列布局的啊!!!
    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
    <div class="1">
    <div class="ele">
    </div>
    <div class="ele">
    </div>
    <div class="ele">
    </div>
    </div>
    <div class="2">
    <div class="ele">
    </div>
    <div class="ele">
    </div>
    </div>
    <div class="3">
    <div class="ele">
    </div>
    <div class="ele">
    </div>
    </div>
    <div class="4">
    <div class="ele">
    </div>
    <div class="ele">
    </div>
    <div class="ele">
    </div>
    </div>

如上我们就把一个一个的横向布局计算absolute位置,变为了,多列等宽布局(只需检测是否要改变列数)每列内部以此顺序布局即可。

参见

流体特性

块状水平元素,如div元素(下同),在默认情况下(非浮动、绝对定位等),水平方向会自动填满外部的容器;如果有margin-left/margin-right, padding-left/padding-right, border-left-width/border-right-width等,实际内容区域会响应变窄。
对于浏览器的元素布局来说最基础的就是一个一个的横向排列,放不下了就另换一行继续进行。

包含块http://w3help.org/zh-cn/kb/008/)

Notably, a containing block is not a box (it is a rectangle), however it is often derived from the dimensions of a box

值得注意的是,一个包含块不是一个盒子(它是一个矩形),然而它通常是从盒子的尺寸派生的

containing block chain

A sequence of successive containing blocks that form an ancestor-descendant chain through the containing block relation. For example, an inline box’s containing block is the content box of its closest block container ancestor; if that block container is an in-flow block, then its containing block is formed by its parent block container; if that grandparent block container is absolutely positioned, then its containing block is the padding edges of its closest positioned ancestor (not necessarily its parent), and so on up to the initial containing block.

  1. position: static/relative 找的是最近的block containers, block container 是包含IFC或者是BFC的

CSS3 transform使position:fixed元素absolute化实例页面

http://www.zhangxinxu.com/study/201505/css3-transform-position-fixed-to-absolute.html

宽度计算的另一种和containing block有关

‘margin-left’ + ‘border-left-width’ + ‘padding-left’ + ‘width’ + ‘padding-right’ + ‘border-right-width’ + ‘margin-right’ = width of containing block

其中margin-left/width/margin-right可为auto,且具有以下规则:

  1. 若width为auto,则其他设置为auto的属性的实际值为0,并让width的实际值满足等式;
  2. 若width为数值,而margin-left/right均为auto,且除marin-left/right外其他属性值总和小于containing block的宽度,那么margin-left == margin-right == (‘border-left-width’ + ‘padding-left’ + ‘width’ + ‘padding-right’ + ‘border-right-width’)/2;否则margin-left == margin-right == 0.

尺寸

相对定位元素的尺寸,会保持它在常规流中的尺寸。包括换行以及原来为它保留的位置。

定位及计算偏移后的值

‘left’ 和 ‘right’ 的特性值

对于一个相对定位的元素,’left’ 和 ‘right’ 会水平的位移框而不会改变它的大小。’left’ 会将框向右移动,’right’ 会将框向左移动。 由于 ‘left’ 或者 ‘right’ 不会造成框被拆分或者拉伸,所以,计算后的值( computed value )总是:left = -right。

  1. ‘left’ 和 ‘right’ 的设定值都是 “auto”

如果 ‘left’ 和 ‘right’ 的值都是 “auto” (它们的初始值),计算后的值( computed value )为 0(例如,框区留在其原来的位置)。

  1. ‘left’ 或 ‘right’ 其一的设定值为 “auto”

如果 left 为 ‘auto’,计算后的值(computed value)为 right 的负值(例如,框区根据 right 值向左移)。 如果 right 被指定为 ‘auto’,其计算后的值(computed value)为 left 值的负值。

示例代码:

1
<div style="width:20px; height:20px; background-color:red; position:relative; left:100px;"></div>

上述代码中,DIV 元素是相对定位的元素,它的 ‘left’ 值是 “100px”, ‘right’ 没有设置,默认为 “auto”,那么,’right’ 特性计算后的值应该是 -left,即 “right:-100px”。

  1. ‘left’ 和 ‘right’ 设定值都不是 “auto”

如果 ‘left’ 和 ‘right’ 都不是 “auto”,那么定位就显得很牵强,其中一个不得不被舍弃。如果包含块的 ‘direction’ 属性是 “ltr”, 那么 ‘left’ 将获胜,’right’ 值变成 -left。如果包含块的 ‘direction’ 属性是 ‘rtl’,那么 ‘right’ 获胜,’left’ 值将被忽略。

示例代码:

1
2
3
<div style="width:100px; height:100px; overflow:auto; border:1px solid blue;">
<div style="width:20px; height:20px; background-color:red; position:relative; left:60px; right:60px;"></div>
</div>

最后,’left’ 应该比较强悍才对。

float

http://efe.baidu.com/blog/float/

  1. float时 position是fixed和absolute则浮动相对于失效
  2. 其他float有效,position失效

浮动元素的特点

  1. 元素被视作块级元素,相当于display设置为“block”;

  2. 元素具备包裹性,会根据它所包含的元素实现宽度、高度自适应;

  3. 浮动元素前后的 ###块级兄弟元素### 忽视浮动元素的而占据它的位置,并且元素会处在浮动元素的下层(并且无法通过z-index属性改变他们的层叠位置),但它的内部文字和其他行内元素都会环绕浮动元素;

  4. 浮动元素前后的行内元素环绕浮动元素排列;

  5. 浮动元素之前的元素如果也是浮动元素,且方向相同,它会紧跟在它们后面;父元素宽度不够,换行展示;

  6. 浮动元素之间的水平间距不会重叠;

  7. 当包含元素中只有浮动元素时,包含元素将会高度塌陷;

  8. 浮动元素的父元素的非浮动兄弟元素,忽视浮动元素存在,覆盖浮动元素;

  9. 浮动元素的父元素的浮动兄弟元素,会跟随浮动元素布局,仿佛处在同一父元素中。

浮动对兄弟元素的影响

Since a float is not in the flow, non-positioned block boxes created before and after the float box flow vertically as if the float did not exist. However, the current and subsequent line boxes created next to the float are shortened as necessary to make room for the margin box of the float.

块级非定位元素自动排列就好像没有浮动元素似的,行内元素则需要为浮动元素腾出位置(对于层级上下文来说,浮动元素高于块级盒子)

浮动元素之间不重叠;尽可能像边缘漂浮,但不越界。

那么第八条、第九条为什么?看CSS标准中的下面的描述:

References to other elements in these rules refer only to other elements in the same block formatting context as the float.

也就是说,float对同一个BFC内的元素有效。如果父元素没有触发生成新的BFC,那么父元素的兄弟元素都算是跟父元素中的元素处于同一BFC,也就会受浮动的影响,并且行为规则与同处于同一个父元素之中的元素的规则相同:块级元素重叠(和float元素重叠);行内元素环绕;浮动元素跟随。

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
<div id="a">
<div id="b">
</div>
</div>

<div id="c">
</div>

#a {
width: 300px;

background: blue;

}

#b {
width: 200px;
height: 10px;
background: red;
float: left;
}

#c {
width: 200px;
height: 100px;
background: green;

}

正是因为浮动元素的这三条特点,因此,在使用了浮动元素以后,通常都要做“清除浮动“或”闭合浮动“的操作,来避免浮动元素对其他元素的影响

flexbox

flex: flex-grow(相对于其他元素的增长比例) flex-shrink(相对于其他元素的缩小比例) flex-baisis(基础大小)

  1. flexbox 无法使用float
  2. 其他定位元素照用
  3. overflow 可以使用
  4. flex-container 回创建一个containing-block
  5. vertical-align无法使用在flex-item

block container

Except for table boxes, which are described in a later chapter, and replaced elements, a block-level box is also a block container box. A block container box either contains only block-level boxes or establishes an inline formatting context and thus contains only inline-level boxes. Not all block container boxes are block-level boxes: non-replaced inline blocks and non-replaced table cells are block containers but not block-level boxes. Block-level boxes that are also block containers are called block boxes.

块级盒子都是block container, 但block container不都是块级盒子,还有非替换行内块级盒子等

indeterminate 属性 只能通过js操作,半赋值

https://imququ.com/post/native-tri-state-checkbox.html

svg 多色图标 https://css-tricks.com/icon-fonts-vs-svg/

css hack各个浏览器的方法

对内 css api

css in js

props(react 社区的 style components):

  1. local namespace & scoped
  2. Smarter critical css extraction // 更精细的加载 css
  3. Dynamic styles
  4. Shared style values
  5. Manageable API(对外提供了可控的 api)

cons:

  1. runtime 级别的无法使用现有的静态 css 处理器
  2. 重新开发工具链

对外的 api,扩展某一个组件

  1. css in js: 预定义样式(className), 以 enum 方式开放, 就可以修改这些 className 的样式
  2. css out of js: 让使用者写 css 样式,这样就无法修改 htmL 结构

全局 样式

样式代码尽可能少的 hard code

互操作的 api

web 本身是开放的

BEM & atomic

css hover 子元素时 父元素也处于 hover 状态

典型的就是菜单,当我们 hover 父元素显示出菜单然后挪到子元素时子元素依然在(采用父元素 hover 子元素 来显示)反例是当你把菜单子元素挪出去放到作为父元素的兄弟元素然后继续使用 hover 显示,你在去挪动就会发现兄弟元素不会再显示了。
()[https://codepen.io/dengshen/pen/erGyaP]

mouseout/mouseleave

mouseout 当从父元素移到子元素时即使没有出父元素也会触发 mouseout
mousleave 即使从父元素移到子元素时 在子元素上移动也不会触发 mouseleave

视差滚动

  1. background-attachment: fixed 滚动背景固定不跟随滚动,改变 background-position 来滚动
  2. onscroll: 改变各个元素的 style.top 来滚动(同上 chrome 会有跳动,因为 chrome 对滚动做了优化,很多次滚动才触发一次 scroll 事件, 相当于多帧时间只有一帧动画)
  3. mousewheel: 同上

滚动性能优化(https://www.cnblogs.com/coco1s/p/5499469.html)

img 标签多三像素的问题(https://github.com/muwenzi/Program-Blog/issues/121)

原因是因为 img 标签是 inline 元素对齐方式是 base-line 和文字的对齐方式一致, 因此设置 font-size: 0

getBoundingClientRect() 来获取页面元素的位置(https://juejin.im/entry/59c1fd23f265da06594316a9)

使用

  1. 获取元素在视窗的位置
1
2
3
4
let box = document.getElementId('box')
const rect = box.getBoundingClientRect()
rect.left 就是元素左边距距离视窗的距离
rect.top 就是元素上边距距离视窗的距离
  1. 获取元素在页面的位置
1
2
3
4
let box = document.getElementId('box')
const rect = box.getBoundingClientRect()
rect.left + document.documentElement.scrollLeft
rect.top + document.docuemntElement.scrollTop
  1. 判断元素是否在可见区域
1
2
3
4
5
6
7
8
9
function isElementInViewport (el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /_or(window.height() _/
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /_or $(window.width() _/
);
}

获取元素的各种高度(https://blog.csdn.net/woxueliuyun/article/details/8638427)

  • clientHeight

    大部分浏览器对 clientHeight 都没有什么异议,认为是元素可视区域的高度,也就是说元素或窗口中可以看到内容的这个区域的高度,即然是指可看到内容的区域,滚动条不算在内。但要注意 padding 是算在内。其计算方式为 clientHeight = topPadding + bottomPadding+ height - 水平滚动条高度。

  • offsetHeight

    在 IE6,IE7,IE8, IE9 以及最新的的 FF, Chrome 中,对于一般元素,都是 offsetHeight = padding + height + border = clientHeight + 滚动条 + 边框

  • scrollHeight

    scrollHeight 的争议比较大,有些浏览器认为 scrollHeight 可以小于 clientHeight,有些认为 scrollHeight 至少应该等于 clientHeight。但有一点是一样的,就是 scrollHeight >= topPadding + bottomPadding + 内容 margin box 的高度。

    在浏览器中的区别在于:
    IE6、IE7 认为 scrollHeight 是内容高度,可以小于 clientHeight。

    FF 认为 scrollHeight 是内容高度,不过最小值是 clientHeight。

    注: 以上都是对于一般元素而方言的,body 和 documentElement 的 clientHeight, offsetHeight 和 scrollHeight 在各个浏览器中的计算方式又不同。

    在所有的浏览器中,如果你想获取视窗可见部分的高度,应该使用 documentElement.clientHeight,因为 body.clientHeight 是由它的内容决定的。

viewport 通常被称作视口(视窗 ),是指设备的屏幕上能用来显示网页的那一块区域

如何获取设备像素(屏幕尺寸)?

通常我们可以从 BOM(Browser Object Model) 中通过 screen.width/screen.height 获取。

如何获取窗口尺寸?

如果你想知道用户访问的页面中有多少空间可以用来 CSS 布局,那么你需要获取浏览器窗口的内部尺寸。可以通过 window.innerWidth/window.innerHeight 来获取这些尺寸。注意度量的宽度和高度是包括滚动条的。它们也被视为内部窗口的一部分。(这大部分是因为历史原因造成的。)

如何获取 HTML 文档的尺寸?

可见宽度:document.documentElement.clientWidth

实际宽度:document.documentElement.offsetWidth

实际上,document.documentElement 指的是 <html> 元素:即任何 HTML 文档的根元素。

layout viewport 和 visual viewport

当我们比较移动浏览器和桌面浏览器的时候,它们最显而易见的不同就是屏幕尺寸。当我们打开一个未针对移动端做任何 CSS 适配的页面时,我们会不由的慨叹:viewport 太窄了!viewport 并不能按照写给桌面浏览器的 CSS 正确布局。明显的解决方案是使 viewport 变宽一些。聪明的人们想到了一个解决办法:把 viewport 分成两部分:visual viewport 和 layout viewport。

两个 viewport 都是以 CSS 像素度量的。但是当进行缩放(如果你放大,屏幕上的 CSS 像素会变少)的时候,visual viewport 的尺寸会发生变化,layout viewport 的尺寸仍然跟之前的一样。

George Cummins 在 Stack Overflow 上对基本概念给出了最佳解释:

把 layout viewport 想像成为一张不会变更大小或者形状的大图。现在想像你有一个小一些的框架,你通过它来看这张大图。(译者:可以理解为「管中窥豹」)这个小框架的周围被不透明的材料所环绕,这掩盖了你所有的视线,只留这张大图的一部分给你。你通过这个框架所能看到的大图的部分就是 visual viewport。当你保持框架(缩小)来看整个图片的时候,你可以不用管大图,或者你可以靠近一些(放大)只看局部。你也可以改变框架的方向,但是大图(layout
viewport)的大小和形状永远不会变。

我们工作中所谓的 CSS 布局,尤其是百分比宽度,是以 layout viewport 做为参照系来计算的,它被认为要比 visual viewport 宽。即:<html> 元素在初始情况下用的是 layout viewport 的宽度,这使得站点布局的行为与其在桌面浏览器上的一样。

layout viewport 有多宽?每个浏览器都不一样。Safari iPhone 为 980px,Opera 为 850px,Android WebKit 800px,最后 IE 为 974px。

如何获取两个 viewport 的宽度?如果理解了上面讲述的内容,不难理解下面获取 viewport 的方式:

  1. layout viewport: document.documentElement.clientWidth/document.documentElement.clientHeight

  2. visual viewport: window.innerWidth/window.innerHeight

canvas 适配retina

关于react

什么 jsx

Fundamentally, JSX just provides syntactic sugar for the React.createElement(component, props, …children) function. The JSX code:

react element vs component

element

Simply put, a React element describes what you want to see on the screen. Not so simply put, a React element is an object representation of a DOM node.’

react element 简单说就是描述了你在屏幕上所看到的,复杂点说就是一个 DOM node 的 js object 表现代理

In order to create our object representation of a DOM node (aka React element), we can use React’s createElement method.

为了创建一个 DOM node 的 js object 表现代理,我们需要使用 react 的 createElement 方法

1
2
3
4
5
const element = React.createElement(
'div',
{id: 'login-btn'},
'Login'
)

而我们所看到的所有如图片里的写法都是 jsx, 当他们被 babel 转义后就是这个

1
React.createElement(Icon, null)

So finally, what do we call it when we write out our component like this, ? We can call it “creating an element” because after the JSX is transpiled, that’s exactly what’s happening.
也就是我们在 jsx 文件里的所有的类似的写法都是在写 createElement()方法(jsx 会被 babel 转义)

如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Button ({ addFriend }) {
return (
<button onClick={addFriend}>Add Friend</button>
)
}

function User ({ name, addFriend }) {
return (
<div>
<p>{name}</p>
<Button addFriend={addFriend}/>
</div>
)
}

会被转为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Button ({ addFriend }) {
return React.createElement(
"button",
{ onClick: addFriend },
"Add Friend"
)
}

function User({ name, addFriend }) {
return React.createElement(
"div",
null,
React.createElement(
"p",
null,
name
),
React.createElement(Button, { addFriend })
)
}

component

“Components are the building blocks of React”. Notice, however, that we started this post with elements. The reason for this is because once you understand elements, understanding components is a smooth transition. A component is a function or a Class which optionally accepts input and returns a React element.

理解了上面的 react element 就知道 component 了,对上段英文翻译一下就是, component 是构建 react 应用的基础,理解了 element 就知道了,component 就是一个类或者是一个函数,他接收输入 并且返回一个 element 以供 react 使用

  1. createElement(type, props, […children])
    type 是 tagName string, 或者 component(class, function) 又或者是 fragment
1
createElement -> 解析props, 生成key或者ref, ->生成vnode

http://blog.csdn.net/liangklfang/article/details/72782920

getSnapshotBeforeUpdate(https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#reading-dom-properties-b=】、【

这个生命周期函数是在 render 之后 DOM 更新之前调用的

若是想要获取 DOM 更新之前的 DOM 数据可以在这个函数里获取

react 关于 props 和 state 的函数或属性

1
2
3
static defaultProps = {} // 默认属性
getDefaultProps , 函数的形式获取默认属性
getInitialState , 函数的形式获取初始state

ts中的keyof 是展示一个对象中的 key

1
2
3
4
5
6
7
8
9
10
11
12
13
keyof {a: 3} // a

type Partial<T> = {
[P in keyof T]?: T[P]; // P是T中的key, 且加了? 就是可选的意思
};

type Required<T> = {
[P in keyof T]-?: T[P]; // -? 就是去掉 ? 去掉可选性, 也可以使+?添加可选属性
};

type Readonly<T> = {
readonly [P in keyof T]: T[P]; // 全部加上readonly属性
};

分布式基础

分布式的三个状态

  1. 成功
  2. 失败
  3. 超时(未响应)
    1. 当超时了可以发起读数据操作,验证是否成功,就好比银行转账,转失败了,他会让你去看看是否成功了,避免多次操作
    2. 操作幂等则可以发起重试操作

tcp 不可靠就是说网络不可靠

  1. 应用程序的消息发给 tcp 协议栈,宕机了,消息没法出去,可是对于应用程序来说它认为消息是发出去了的。

异常处理黄金原则: 任何在设计阶段考虑到的情况都会在实际系统中发生;在实际运行中发生的异常反而没有在设计阶段想到。因此不要放过,设计阶段想到的任何异常。

副本

副本(replica/copy)指在分布式系统中为数据或服务提供的冗余。

副本一致性

系统通过副本控制协议,是得从系统外部读取内部各个副本的数据在一定条件下,读到的数据相同称之为副本一致性(consistency)。

  1. 强一致性(strong consistency) 无论怎么读都 ok
  2. 单调一致性(monotonic consistency):任何时刻,任何用户一旦读到某个数据在某次更新后的数据,这个用户就不会再读到比这个值更旧的值。单调一致性弱于强一致性。确实非常实用的一种一致性。因为通常来说,用户只关心自己读到的数据,而不会关心其他人的情况。
  3. 会话一致性(session consistency):任何用户在某一次会话内一旦读到某个数据某次更新后的值,在此次会话中就不会再读到比这个值更旧的值。这个一致性比单调一致性再稍弱一些。会话一致性只保护了单个用户在单次会话内的数据一致性,不同永不不同会话之间的一致性没有保障。例如 php 中的 session 概念。可以将数据版本号等信息保存在 session 中,读取数据时验证副本的版本号,只读取版本号大于等于 session 中版本号的副本,从而实现会话一致性
  4. 最终一致性(eventual consistency):就是一旦更新了数据,各个副本最终将达到完全一致。若用户在一个副本上一直读取可以达到类似单调一致性的效果,但若是换个副本去读取就无法保证了。
  5. 弱一致性(week consistency):一旦某个更新成功,用户无法在一个确定时间内读到这次更新的 值,且即使在某个副本上读到了新的值,也不能保证在其他副本上可以读到新的值。弱一致性系统 一般很难在实际中使用,使用弱一致性系统需要应用方做更多的工作从而使得系统可用。

衡量分布式系统的指标

  1. 性能
  • 系统的吞吐能力,指系统在某一时间可以处理的数据总量,通常可以用系统每秒处理的总的数据量来衡量;
  • 系统的响应延迟,指系统完成某一功能需要使用的时间;
  • 系统的并发能力,指系统可以同时完成某一功能的能力,通常 也用 QPS(query per second)来衡量。上述三个性能指标往往会相互制约,追求高吞吐的系统,往往 很难做到低延迟;系统平均响应时间较长时,也很难提高 QPS
  1. 可用性
  2. 可扩展性
  3. 一致性

分布式系统原理

数据分布方式

  1. 哈希方式

    • 缺点:1.哈希分布数据的缺点同样明显,突出表现为可扩展性不高,一旦集群规模需要扩展,则几乎所 有的数据需要被迁移并重新分布,因为所有的数据都需要再次进行哈希,而哈希的结果则有可能就不同了。工程中,扩展哈希分布数据的系统时,往往使得集群规模成倍扩 展,按照数据重新计算哈希,这样原本一台机器上的数据只需迁移一半到另一台对应的机器上即可 完成扩展。2.哈希分布数据的另一个缺点是,一旦某数据特征值的数据严重不均,容易出现“数据倾斜”(data skew)问题。导致某些机器上的数据过多
  2. 按数据范围分布,比如用户 id[0-100],30 个一分区,工程中,为了数据迁移等负载均衡操作的方便, 往往利用动态划分区间的技术,使得每个区间中服务的数据量尽量的一样多。一般的,往往需要使用专门的服务器在内存中维护数据分布信息, 称这种数据的分布信息为一种元信息。实际工程中,一般也不按照某一维度划分数据范围,而是使用全部数据划分范围,从而避免数 据倾斜的问题。

  3. 按数据量分布,就是把固定大小的数据放在一起,好比 linux 中的 page,一个 page 一管理
  4. 一致性哈希,一致性哈希的基本方式是使用一个哈希函数计算数据或数据特征的哈希值,令该哈希函数的输出值域为一个封闭的环,即哈希 函数输出的最大值是最小值的前序。将节点随机分布到这个环上,每个节点负责处理从自己开始顺 时针至下一个节点的全部哈希值域上的数据。一致性哈希 的优点在于可以任意动态添加、删除节点,每次添加、删除一个节点仅影响一致性哈希环上相邻的 节点。

为此一种常见的改进算法是引入虚节点(virtual node)的概念,系统初始时就创建许多虚节点, 虚节点的个数一般远大于未来集群中机器的个数,将虚节点均匀分布到一致性哈希值域环上,其功能与基本一致性哈希算法中的节点相同。为每个节点分配若干虚节点。操作数据时,首先通过数据 的哈希值在环上找到对应的虚节点,进而查找元数据找到对应的真实节点。使用虚节点改进有多个 优点。首先,一旦某个节点不可用,该节点将使得多个虚节点不可用,从而使得多个相邻的真实节 点负载失效节点的压里。同理,一旦加入一个新节点,可以分配多个虚节点,从而使得新节点可以 负载多个原有节点的压力,从全局看,较容易实现扩容时的负载均衡。(原理是增加很多的虚拟节点,再将虚拟节点对应到真实节点参见)

副本与数据分布

  1. 以机器为单位进行数据冗余,就是有 a,b,c 三台机器,b,c 分别有 a 的全量数据,但是有缺点 1.宕机恢复,若 b 挂了,就得从 a,或 c 进行全量数据同步,效率低下。2.b 挂了以后,a,c 的负载就高了,b 的负载就全到了 a,c 上了
  2. 以数据块为单位进行数据冗余,将数据拆为较合理的数据段,以数据段为单位作为副本。实践中,常常使得每个数据段的大小尽量相等且控制在一定的大小以内。数据段有很多不同的称谓,segment,fragment,chunk,partition 等等。以数据段为单位的副本一旦副本分布与机器无关,数据丢失后的恢复效率将非常高,可以同时从多太物理机 copy 数据。工程中,完全按照数据段建立副本会引起需要管理的元数据的开销增大,副本维护的难度也相 应增大。一种折中的做法是将某些数据段组成一个数据段分组,按数据段分组为粒度进行副本管理。 这样做可以将副本粒度控制在一个较为合适的范围内。

本地化计算 移动数据不如移动计算

基本副本协议

  1. 中心化副本协议
    1. primary-secondary 协议 * Primary-secondary 协议的数据更新流程 1. 数据更新都由 primary 节点协调完成。 2. 外部节点将更新操作发给 primary 节点 3. primary 节点进行并发控制即确定并发更新操作的先后顺序 4. primary 节点将更新操作发送给 secondary 节点 5. primary 根据 secondary 节点的完成情况决定更新是否成功并将结果返回外部节点有些系统(例如,GFS),使用接力的方式同步数据, primary 同步给 secondary1, secondary1 同步给 secondary2
  2. 去中心化副本控制协议,与中心化副本系统协议最大的不同是,去中心化副本控制协议没有中心节点,协议中所有的节点都是完全对等的,节点之间通过平等协商 达到一致

Lease 机制 (租赁机制)

lease 机 制最重要的应用:判定节点状态。

基于 lease 的分布式 cache 系统

基本的问题背景如下:在一个分布式系统中,有一个中心服务器节点,中心服务器存储、维护 着一些数据,这些数据是系统的元数据。系统中其他的节点通过访问中心服务器节点读取、修改其 上的元数据。由于系统中各种操作都依赖于元数据,如果每次读取元数据的操作都访问中心服务器 节点,那么中心服务器节点的性能成为系统的瓶颈。为此,设计一种元数据 cache,在各个节点上 cache 元数据信息,从而减少对中心服务器节点的访问,提高性能。另一方面,系统的正确运行严 格依赖于元数据的正确,这就要求各个节点上 cache 的数据始终与中心服务器上的数据一致,cache 中的数据不能是旧的脏数据。最后,设计的 cache 系统要能最大可能的处理节点宕机、网络中断等 异常,最大程度的提高系统的可用性。

lease cache 的实现原理

  1. 首先假设中心服务器与节点之间的时间同步。中心服务器向 cache 节点发送数据的同时下发一个 lease,每个 lease 都一个过期时间,并且这个过期时间是一个明确的时间点,例如 12:00 一旦过了这个时间,那么所有的缓存数据都将过期,lease 失效。这也意味着 lease 的过期时间与发放时间无关,也就是说有可能节点收到数据时 lease 就已经过期了。中心发出的 lease 的含义是:在 lease 时间内服务器保证不修改数据。

    1. cache 节点收到 lease 以及数据后,把数据加入 cache,所有的在 lease 时间内的读请求都可以直接返回
    2. 当 lease 到期后,清掉本地缓存,并向中心服务器发出获取数据的请求,此时到 cache 节点的请求都会被阻塞,直到中心服务器返回新的数据以及 lease
    3. 当修改数据时,修改请求发到中心服务器,此时应当等到所有发出的 lease 都过期后,再修改中心服务器,并且再把数据发给 cache 节点。此时若是有 cache 的读请求,应当阻塞

    读流程:判断元数据是否已经处于本地 cache 且 lease 处于有效期内
    1.1 是:直接返回 cache 中的元数据
    1.2 否:向中心服务器节点请求读取元数据信息
    1.2.1 服务器收到读取请求后,返回元数据及一个对应的 lease
    1.2.2 客户端是否成功收到服务器返回的数据
    1.2.2.1 失败或超时:退出流程,读取失败,可重试
    1.2.2.2 成功:将元数据与该元数据的 lease 记录到内存中,返回元数据

    修改流程:

    1. 节点向中心服务器发起修改元数据请求。
    2. 服务器收到修改请求后,阻塞所有新的来自 cache 的读数据请求,即接收读请求,但不返回数据。
    3. 服务器等待所有与该元数据相关的 lease 超时。
    4. 服务器修改元数据并向客户端节点返回修改成功。优化
    5. 中心服务器收到修改请求时,会阻塞所有的新的来自 cache 节点。这么做是为了防止一直在发放 lease, 导致一直无法等到所有的 lease 过期。但是我们可以不阻塞,直接返回新的数据却不发放 lease。
    6. 中心服务器可以不等待,而是主动通知各个 cache 节点数据过期,让 lease 失效,若是所有节点均返回 true,则可以进行更新,若有一个返回 false 则不可以更新。经过这两个优化,则可以大大提高性能,cache 的数据可以随时丢弃,可是副本的数据却不可以丢弃。

lease 机制的分析

首先给出本文对 lease 的定义:Lease 是由颁发者授予的在某一有效期内的承诺。颁发者一旦发 出 lease,则无论接受方是否收到,也无论后续接收方处于何种状态,只要 lease 不过期,颁发者一 定严守承诺;另一方面,接收方在 lease 的有效期内可以使用颁发者的承诺,但一旦 lease 过期,接 收方一定不能继续使用颁发者的承诺。

由于 lease 是一种承诺,具体的承诺内容可以非常宽泛,可以是上节的例子中数据的正确性;也 可以是某种权限,例如当需要做并发控制时,同一时刻只给某一个节点颁发 lease,只有持有 lease 的节点才可以修改数据;也可以是某种身份,例如在 primary-secondary(2.2.2 )架构中,给节点颁发 lease,只有持有 lease 的节点才具有 primary 身份。Lease 的承诺的内涵还可以非常宽泛,这里不再 一一列举。

关于时钟同步问题可以让 client 在申请 lease 时带上自己的时间戳,server 判断若是相差太大就不允许接入

基于 lease 机制确定节点状态

分布式主要是 3 点

  1. 节点 – 系统中按照协议完成计算工作的一个逻辑实体,可能是执行某些工作的进程或机器
  2. 网络 – 系统的数据传输通道,用来彼此通信。通信是具有方向性的。
  3. 存储 – 系统中持久化数据的数据库或者文件存储。

心跳无法解决节点状态问题

是指分布式系统的状态,点对点的还是可以使用的

lease 的有效期时间选择

Lease 的有效期虽然是一个确定的时间点,当颁发者在发布 lease 时通常都是将当前时间加上一 个固定的时长从而计算出 lease 的有效期。如何选择 Lease 的时长在工程实践中是一个值得讨论的问 题。如果 lease 的时长太短,例如 1s,一旦出现网络抖动 lease 很容易丢失,从而造成节点失去 lease, 使得依赖 lease 的服务停止;如果 lease 的时长太大,例如 1 分钟,则一旦接受者异常,颁发者需要 过长的时间收回 lease 承诺。例如,使用 lease 确定节点状态时,若 lease 时间过短,有可能造成网络 瞬断时节点收不到 lease 从而引起服务不稳定,若 lease 时间过长,则一旦某节点宕机异常,需要较 大的时间等待 lease 过期才能发现节点异常。工程中,常选择的 lease 时长是 10 秒级别,这是一个经 过验证的经验值,实践中可以作为参考并综合选择合适的时长。

一致性种类

于是就有人提出相对弱一点的一致性模型,这些模型包括:线性一致性,原子一致性,顺序一致性,缓存一致性,静态一致性,处理器一致性,PRAM一致性,释放一致性,因果一致性,TSO一致性,PSO一致性,弱序一致性,本地一致性,连续一致性等等,当然,也包括我们要详细介绍的最终一致性。

https://pure-earth-7284.herokuapp.com/2016/02/14/talk-about-consistency/

quicksort

快速排序时为什么一定要把pivot放到头或尾最后在放回去呢?

因为我们在把头尾index往里缩时我们判断的是小于和不小于(大于同理),比如我们把pivot 5 放在中间,当我们的头index判断不小于时,若是到了pivot条件也是成立的就跳到了pivot右侧,此时若是尾index也在右边发现了小于pivot的值在交换时就会出现,小于pivot的值出现在pivot的右侧,当然我们也可以做额外操作,判断若是头index到了pivot的位置时则不再继续

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. 
| |
V V
4, 5, 8, 2
头 尾

2.
| |
V V
4, 5, 2, 8
头 尾

3. 此时找到了大于以及小于的pivot的值交换
4,5,2,8, 显然出问题了
、、、、、、
但是若是5在头部
5,4,8,2, 交换就没事了,因为接下来会有把pivot和low或者high交换的过程

我们每进行一次交换就会找到一个元素的正确位置,此时接下来再排序时就不要再把他算进去了

1
2,1,2,1,3,4,5

上述3已经在正确位置了,再接下来的排序应当是4,5和2,1,2,1两组进行排序而不是3,4,5和2,1,2,1或者4,5和2,1,2,1, 3进行排序, 因为3已经是在正确位置了,没必要再排一次。

我们可看看golang源码的排序

  1. 若是小于12个元素时它采用shell希尔排序
  2. 他会计算一个深度2 * lg(n+1), 然后采用快速排序,没进行一次就深度减一,当为0时就用堆排序,至于这个数字咋算出来的可以查查

解析:元素较少时,大家都差不多,反而是希尔排序这样的好一些,好在哪啊?
我们都知道快排在元素基本有序时是最慢的最坏能达到o(n^2),所以当序列基本有序时我们可以采用堆排序

一个简单的快排版本

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
package main
import (
"fmt"
)

// threads 线程标识创建线程的个数
func quicksort(nums []int, length int) {
if length <= 1 {
return
}

left := 1
right := length - 1

pivot := nums[0]

for{
for; left < right && nums[right] > pivot; right--{ }
for; left < right && nums[left] <= pivot; left++ { }
if left >= right {
break
}
nums[left], nums[right] = nums[right], nums[left]
}
nums[0], nums[left] = nums[left], nums[0]

quicksort(nums[0:left], left) //分任务
temp := nums[left + 1: length]
quicksort(temp, len(temp))
return
}

func main() {
x := []int{3, 41, 24, 76, 11, 45, 3, 3, 64, 21, 69, 19, 36}
quicksort(x[:], len(x))
fmt.Println(x);
}

hyperledger-fabric基础

流程问题

关于使用 kafka 实现共识

重新部署 chaincode 时要删除 chaincode 生成的 docker 镜像

fabric-ca 就是发证方,类似于银行发行信用卡。http://hyperledger-fabric.readthedocs.io/en/latest/identity/identity.html

1
A PKI is like a card provider – it dispenses many different types of verifiable identities. An MSP, on the other hand, is like the list of card providers accepted by the store – determining which identities are the trusted members (actors) of the store payment network. MSPs turn verifiable identities into the members of a blockchain network.

MSPs 把可验证的身份证明集成到了区块链网络的成员中。

anchor peer

channel 上的其他节点都能够发现他并和他进行交流,并且 channel 上的每个 member 都会有 anchor peers(注意复数可以不只有一个 anchor peer),这样所有的 peer(注意所有的 peer 就是指所属不同的 memer 的 peer 也是可以相互发现的) 之间可以通过 anchor peer 互相发现。

只读的 chaincode 调用客户端一般是不会将其记录进 ledger 的,除非客户端明确要求。

transaction

我们的 invoke(显然 query 也是 invoke 就意味着 query 也是可以提交到 ledger 的)和 instantiate 都成为 transaction 客户端从背书节点收集返回结果并将其打包提交到 ledger。

configtx.yml

  • Consortium 联盟

系统链码

  • cscc:负责 joinchannel/config update 等
  • escc:负责对传入数据进行签名(msp 管理)
  • lccc:负责 deploy invoke
  • vscc:负责签名验证/策略验证(这里如何进行策略验证?)
  • qscc:负责 ledger 查询

A principal is described in terms of the MSP that is tasked to validate the identity of the signer and of the role that the signer has within that MSP. Currently, two roles are supported: member and admin. Principals are described as MSP.ROLE, where MSP is the MSP ID that is required, and ROLE is either one of the two strings member and admin. Examples of valid principals are ‘Org0.admin’ (any administrator of the Org0 MSP) or ‘Org1.member’ (any member of the Org1 MSP).(http://hyperledger-fabric.readthedocs.io/en/latest/endorsement-policies.html)

configtx.yml

  • Profiles
  • Organizations
  • Application
  • Orderer
  • Capabilities
  • Resources

policy The primary purpose of this document is to explain how policies are defined in and interact with the channel configuratio

  • UNKNOWN = 0; // Reserved to check for proper initialization
  • SIGNATURE = 1; 使用
  • MSP = 2;
  • IMPLICIT_META = 3; 使用

MSP OU organizationalUnitName

msp config

  • a folder admincerts to include PEM files each corresponding to an administrator certificate
  • a folder cacerts to include PEM files each corresponding to a root CA’s certificate
  • (optional) a folder intermediatecerts to include PEM files each corresponding to an intermediate CA’s certificate
  • (optional) a file config.yaml to configure the supported Organizational Units and identity classifications (see respective sections below).
  • (optional) a folder crls to include the considered CRLs
  • a folder keystore to include a PEM file with the node’s signing key; we emphasise that currently RSA keys are not supported
  • a folder signcerts to include a PEM file with the node’s X.509 certificate
  • (optional) a folder tlscacerts to include PEM files each corresponding to a TLS root CA’s certificate
  • (optional) a folder tlsintermediatecerts to include PEM files each corresponding to an intermediate TLS CA’s certificate

Unlike today’s systems, where a participant’s private programs are used to update their private ledgers, a blockchain system has shared programs to update shared ledgers.

不像现在的那些系统,参与者用他们私有的程序修改他们私有的账本,而区块链系统则是使用共享的程序修改共享的账本。

hyperledger 是由他们的参与者管理他们的交易

participants manage their transactions

hyperledger 不是一个公开的任何人都可以进行交易的系统,他是需要登录管理的,依靠 Membership Service Provider(MSP)来管理。

多 channel 机制可以对参与者的信息进行隔离管理。

hyperledger fabric 账本子系统包括 world state 和 transaction log 两部分。

  • world state 用来描述某一指定时间点时 ledger 的状态。他是 ledger 的数据库。
  • transaction log 记录了所有导致 world state 状态发生改变的 transaction

smart contracts

只能合约是以 chaincode 的形式体现的,当外部 application 想和 blockchain 交互时都是通过 chaincode, chaincode 和 blockchain 交互则是通过 world state 来进行的。并不是直接和 transaction log 交互的。

区域内信任即私链

The ledger is the sequenced, tamper-resistant record of all state transitions in the fabric

防篡改记录

features

每一个 channel 有一个 ledger, 对于参与到 channel 的 peer 他们都是这个 channel 的 member 并且会有一份 ledger 的 copy

  • 查询更新可以基于 key, 范围查询,组合 key 查询
  • 只读的查询可以提供丰富的查询语句(world state 使用 CouchDB)
  • 当启用数据来源功能时,可以查询某一 key 的历史数据
  • 交易包括了 chaincode read set 和 write set 中 key/value 的版本号
  • transaction 包含所有 peer 的签名,并且提交给 ordering service
  • transaction 在 blocks 中是有序的
  • peers 使用背书策略验证交易并执行这些策略
  • 采用多版本管理,执行 chaincode 操作时会验证在执行时间内状态是否被改变过
  • 交易一旦通过并且提交,则不允许在被改变
  • 一个 channel 的 ledger 会有一个配置块来定以策略,acl 以及其他信息
  • channel 有 MSP 来

msp 目录

  • 组织
    • ca 组织的根证书和对应的私钥
    • msp 代表该组织的身份信息
      • admincerts 组织管理员的身份验证证书,被根证书签名
      • cacerts 组织的根证书
      • tlscacerts 用于 tls 的 ca 证书,自签名
    • peers
      • peer0 第一个 peer 的信息
        • msp
          • admincerts 组织管理员的身份验证证书。peer 将基于这些证书来验证交易签署者是否为管理员
          • keystore 本节点自己的身份私钥,用来签名
          • signcerts 验证本节点签名的证书,被组织更证书签名
    • users
      • 存放属于该组织用户的实体

configtx.yml

  • Consortiums Order 所服务的联盟列表
  • Consortium 该应用通道锁关联的联盟的名称

MSP 分为 local msp 和 channel msp

  • local msp 就是给 peer, orderer and users 使用的只针对其自身
  • channel msp 则是 channel 使用的多个组织都回去使用的,每个 peer 节点都会有一个 copy

identity

  • cert
  • public key
  • IdentityIdentifier: { msg.name, 以及一个唯一 id }

一个区块链网络拥有不同的参与者,包括 peer, orderer, client applications, administrators 等等。每一个参与者都会有一个身份证(Identity)以 x.509 数字证书的形式封装。这些身份证很重要,因为他决定了你对于整个区块链网络资源的权限。hyperledger 使用参与者身份证中的一些属性来确定权限,hyperledger 给他们定了一个特殊的名字:principal。Pricipals 就好比是 userIDs 或者是 groupIDs,但是他更灵活,因为他可以包含很多关于参与者身份的属性。当我们说 principals 的时候,我们就是在参与者在系统中的决定参与者权限的身份属性。这些属性通常是 organization, organizational unit, role 或者就是参与者的身份证。

还有参与者的身份证得是合法的被验证过的,并且是来自被系统所信任的权威组织。MSP(memebership service provider)就是用来验证 Identity 的是否是被信任的,更详细的说就是,MSP 是用来表示组织中成员关系规则的模块。MSP 定义了组织中成员身份(Identity)是否有效的规则。默认的 MSP 使用 X.509 证书来作为身份证,并且整个体系结构采用传统的 PKI 模型。

Identity 是身份证,CA 是发放身份证的,MSP 是验证身份证的。

msp 最多包含 9 项

  • root CAs(cacerts): 包含根证书,不只一个,可以有多个,对具体的 node 来说可能只有一个,就是 orgs 的根证书而对于一些 orgs 来说则有可能有很多个根证书,他标识了哪些 CAs 是属于相同的 orgs。(必有)
  • Intermediate CAs(intermediatecerts): 同上中间级的 CA
  • Organizational Units (config.yaml): 是用来严格指定 orgs 成员,若是没有则所有可以通过根 CA 认证的都被认为是相同组织的。

    是需要在根证书中有体现的, 也就是在下发证书时是要表明你是属于哪个 ou 的, 这里还有问题就是可以根据具体的 node 的类型来设置 ou, 只有两类: client 和 peer

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    OrganizationalUnitIdentifiers:

    Certificate: "cacerts/cacert.pem"
    OrganizationalUnitIdentifier: "COP"

    NodeOUs:

    Enable: false

    ClientOUIdentifier: # if Certificate is empty, then the certifier identifier will not be enforced

    Certificate: "cacerts/cacert.pem"
    OrganizationalUnitIdentifier: "OU_client"

    PeerOUIdentifier:
    Certificate: "cacerts/cacert.pem"
    OrganizationalUnitIdentifier: "OU_peer"
    ```
  • Administrators(admincerts): 管理的证书(必选)

  • Revoked Certificates(crls): 被撤销的证书
  • Node Identity(signcerts): 自身的身份识别证书,被组织根证书签过名的(必选)
  • keystore(keystore): 上述证书身份认证证书的私钥(必选)
  • tls root CA(tlscacerts): tls 链接使用的证书(必选)
  • TLS Intermediate CA(tlsintermediatecerts):未见到是 tls 中间层的证书

资源权限的确认是用 principal 来确定

一共有以下几类 pricinpal

  • MSPPrincipal_ROLE:角色类, 参与者在网络的角色

    • admin
    • member
    • peer
    • client
  • MSPPrincipal_ORGANIZATION_UNIT:组织类,参与者在网络中的组织

  • MSPPrincipal_IDENTITY:身份证类,具体到某一个参与者的权限设置

application 与 fabric 的交互

app 要和 fabric 交互是需要通过 peer 的

  1. connect to peer
  2. invoke chaincode(proposal)
    1. peer invokes chaincode with proposal
    2. chaincode generates query or update proposal response
  3. proposal response
  4. request that transaction is ordered
    1. transactions sent to peers in blocks( from order to peers)
    2. peer updates ledger using transaction blocks
  5. ledger update event

通过以上我们得出 我们的客户端想要和 fabric 交互是先连接到 peer, 可 MSP 还记得否,因此 app 必须得有和 MSP 同一个 CA 下发的证书。

还有我们知道同一个组织的 peer 的证书是被同一个根证书所信任的,意味着,同一个组织间的 peer 的才可以进行 gossip 通信。不同组织间的通信通过 anchor peer 进行。

configtxgen

configtx.yml

顶层

  • Profiles map[string]*Profile: 包括 order 以及 channel 配置
  • Organizations []*Organization:各个组织的配置(公共配置)

    • Name string yaml:"Name"
    • ID string yaml:"ID"
    • MSPDir string yaml:"MSPDir"
    • MSPType string yaml:"MSPType"
    • AdminPrincipal string yaml:"AdminPrincipal"
      • AdminRoleAdminPrincipal = “Role.ADMIN”
      • MemberRoleAdminPrincipal = “Role.MEMBER”
    • AnchorPeers []*AnchorPeer yaml:"AnchorPeers"
  • Application *Application:应用通道的配置(公共配置)

  • Orderer *Orderer:order 配置(公共配置)
  • Capabilities map[string]map[string]bool (未知)(个人理解绝得他应该是指定 channel 或者其他实体对某项功能是否支持)
  • Resources(未知):权限判定
    • 目前看到 Toplevel 有 resource, 还有 application 有 resource, 同以前讲的路径权限类似, /channel/application/admins

configtxgen 工具生成 tx 文件依靠的就是 profile 中的配置项。

policy 规则

  • Policy_UNKNOWN Policy_PolicyType = 0
  • Policy_SIGNATURE Policy_PolicyType = 1: n_out_of 规则
  • Policy_MSP Policy_PolicyType = 2:就是 msp 证书验证规则(这里又有 MSPPrincipal role 角色规则判定)
  • Policy_IMPLICIT_META Policy_PolicyType = 3:路径判定规则

    比如:Admins 意味着只有某个实体或者组织的管理员才能操作
    /channel/order/Admins 意味只有 channel 中的 order 的管理员才能操作

ModPolicy 是指对策略的修改权限

国家部门规定药企需要自建追溯系统

医药(溯源),供应链金融

群托管的公共交换协议

专业投资者(专业投资者可以做一些投资策略的事情但也以区块链的形式存储)与普通投资者的交互平台(Dao 以太坊)

  • 发基金
  • 购买

安全的智能合约

形式化验证技术

  • 定理证明(定理库)
  • 模型检测(模型爆炸,程序太长)

hyperledger 的密码算法应用场景

  • 数字证书做身份认证

client 和 peers 之间的节点的交易数据是会被加密的,order 是无发探查到 client 提交的交易信息

bccsp 统一密码服务入口

接口描述

  • 秘钥声明周期管理
    • keyGen
    • 秘钥派生(通过一个秘钥生成另一个秘钥)
    • 秘钥导入
    • 获取秘钥
  • 秘钥操作

    • 验签
    • 签名
    • hash

实现方案

  • software
  • pkcs11 硬件(保证私钥不出设备)
  • plugin(自实现 bccsp)

msp

  • Indentity 接口, 同上所描述的身份
  • 可以从 channel 中获取其他 org 的 msg 信息

国密算法(是国内一些领域的准入门槛)

  • sm2 椭圆曲线算法
  • sm3 hash 算法
  • sm4 对称算法

可以通过扩展 fabric 的 sw 来增加国密算法

还可以使用硬件的方法实现

还有 plugin 的方式实现 bccsp 的实现

国密算法加入-msp 的支持

  • go 的运行时环境改造
  • go lib 层更改
  • 重新实现 msp
  • 也实现 msp 的插件形式

国密算法的其他支持

  • 工具链支持

  • ca 支持

  • sdk 支持

msp 还可以根据 x.509 的属性来判断

  • 1.1.0 中 增加了一个新的属性,可以在 chaincode 中调用接口来获取属性

cryptogen 的工具是没有中间 CA 的

CA

  • 推荐加上中间 CA
  • 使用 fabric-ca
  • 使用 可信第三方权威 CA 作为根 CA, fabric-ca 作为中间 CA
  • 全部使用外部 CA,应当支持 fabric 所支持的 rest api,以及 x.509 的实现,添加属性等

用户操作

  • regist 注册
  • enroll 登录(登录后需要把返回的 secret 等信息保存在本地)
  • reenroll(快到期的是后)
  • revoke(吊销证书, 需要把吊销的证书放到 msp 的目录下)

msp 分为

  • localmsp
  • channelmsp

msp 与 创世块

  • 各个组织的 msp 信息都会被打倒创世块中

idmex 是另一种 bccsp 的实现

添加 admins 需要在 fabric-ca 中注册一个新的用户,在把他的证书 copy 到 msp 的 amdincerts 中

1.1.0 中 Identity 增加了很多借口

transientMap 是可以被 chaincode 的代码访问到的,可以提供一些加密秘钥或者是其他的一些东西,但他们都不会记录到 ledger 的。

登录过期问题

用户登录以后缺少检查登录过期的问题

背书策略有如下三类,在创建 channel 的时候就确定了

  • role: memeber, admin
  • ou: 部门名称
  • indenti

初始块解析出来以后可以看到很多的策略权限,但是目前还没看到可以有设置修改的位置

所有的镜像版本号得一致若是 1.1.0 的那就都得是 1.1.0 的,比如 fabric-ccenv

客户端链码安装时 go 版本是会去 GOPATH 下面去找的

history 查询需要开启 history db

一次 chaincode 调用,可以调用多次 putState

1
2
3
putState('k1', '1');
putState('k2', '2');
putState('k3', '3');

链码(不同 channel 之间也可以)之间可以互相调用使用 invokeChaincode 但是只能是只读的。

kafka 排序最少需要 4 个节

ca-server 需要先初始化, 再启动

实例化 chaincode 时可以指定,背书策略

如下,指定 admin 和 member 然后指定由谁来背书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'endorsement-policy': {
identities: [
{ role: { name: 'member', mspId: ORGS['org1'].mspid } },
{ role: { name: 'member', mspId: ORGS['org2'].mspid } },
{ role: { name: 'admin', mspId: ORGS['org1'].mspid } }
],
policy: {
'1-of': [
// { 'signed-by': 2},
// { '2-of': [{ 'signed-by': 0}, { 'signed-by': 1 }]}
{ 'signed-by': 0 },
{ 'signed-by': 1 }
]
}
}

没有加入 channel 的 peer 是可以安装 chaincode 的但是无法实例化指定 channel 的 chaincode。

peer 的配置

  • CORE_VM_DOCKER_HOSTCONFIG_NETWORKMODE=fixtures_default(当前目录名_network 名字) 很重要关系到 chaincode 容器是否能链接到相应的 peer

fabric 网络中加入新的 peer,可以不需要再重新配置 config, 只需要相应的 org 向他颁发证书即可

  1. msp
    • admincerts 是 org 的 cert
    • cacerts 是 org 的 ca 的 cacerts(最顶层的根证书)
    • peer 自己的证书由 org 的 ca 签发
    • 还有对应的私钥
    • tlscacerts 是 org 的 tlscacert(最顶层的根证书)
  2. tls
    • ca.crt 由 org 的 tlscacert 颁发
    • server.crt 由 ca.crt 颁发

颁发的 x509 证书的域名是要和节点的域名相匹配

例如我给 order 节点颁发的是 example.com 这个域名那么,order 机器对外的域名就得是 example 的子域名,若 order 对外的是 ip,则证书失效,证书里面必须体现 ip 才可以。

hyperledger 的 gossip 协议 简介

  1. 当我们配置 peer.gossip.orgLeader = true, 那么每个节点启动后都会去和 orderer 连接并接收消息
  2. 当我们配置 peer.gossip.userLeaderElection = true, 那么组织内配的节点启动后, 会参与 leader 的选举,选出一个 leader 和 orderer 进行通信,其他节点则是通过 leader 节点同步小心。

上面两项属于 bootstrap 配置,与 anchor peer 无关,anchor peer 是用来和其他组织进行通信的接口