微信H5开发历程记录

wechat

登录授权

  • 微信OAuth:在微信下使用oauth进行用户授权,以便获取用户信息,服务端要配置appID和secret,当用户进入H5页面时候,发现没有授权,就引导用户进行跳转链接授权,这时候会跳转url到
1
https://open.weixin.qq.com/connect/oauth2/authorize?appid=${config.appId}&redirect_uri=${encodeURIComponent(config.redirectUrl)}&response_type=code&scope=snsapi_userinfo&state=1#wechat_redirect

redirectUrl是表示授权后的回调路由跳转,然后回调路由微信会在url里面注入code和state,凭借code和state后端可以向微信获取用户的access_token,要获取用户信息,要使用access_token

  • 向微信请求用户信息
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
import nconf from 'nconf'
import Promise from 'bluebird'
const request = Promise.promisifyAll(require('request'));

const getToken = async (code) => {
const url = `${nconf.get('wechat:baseUrl')}/sns/oauth2/access_token?appid=${nconf.get('wechat:appId')}&secret=${nconf.get('wechat:appSecret')}&code=${code}&grant_type=authorization_code`;
const res = await request.getAsync({
url,
json: true
})
const body = res.body
if (!body.access_token) {
return Promise.reject(new Error(body.errcode +'/'+ body.errmsg))
}
return body
}
const getUserInfo =async (token, openid) => {
const url = `${nconf.get('wechat:baseUrl')}/sns/userinfo?access_token=${token}&openid=${openid}&lang=zh_CN`
const res = await request.getAsync({
url,
json: true
})
const body = res.body
if(!body.openid) {
return Promise.reject(new Error(body.errcode +'/'+ body.errmsg))
}
return body
}

export {
getToken,
getUserInfo
}

配置

  • JS-SDK设置:微信的js-sdk调用比较简单,但是配置需要注意安全域名

安全域名
切记:只能填写域名或者ip,不要多加其他字段

安全域名
切记:这个地方还要设置安全域名,坑爹!一开始不知道

  • 微信的js-sdk需要config,这时候需要服务器提供签名认证,使用wechat-api自动获取access_token(注意保存,不能多次频繁获取)

服务器认证

服务器

微信需要对你的服务器进行认证,你的服务器需要进行http或者https的响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { exchangeToken, getJSConfig } from '../controllers/wechat';
import nconf from 'nconf'
const crypto = require("crypto");

module.exports = (router, commonRouter, authRouter, wechatRouter) => {
wechatRouter.post('/exchangeToken', exchangeToken);
wechatRouter.get('/getJSConfig', getJSConfig);
wechatRouter.get('/', function(req, res) {
let signature = req.query.signature,
timestamp = req.query.timestamp,
nonce = req.query.nonce,
echostr = req.query.echostr;
let array = [nconf.get('wechat:token'), timestamp, nonce];
array.sort();
let tempStr = array.join("");
const hashCode = crypto.createHash("sha1");
let resultCode = hashCode.update(tempStr, "utf8").digest("hex");
if (resultCode === signature) {
res.send(echostr);
} else {
res.send("mismatch");
}
});
};

要点注意

  • vue.js History路由和后台接口冲突问题:vue前端路由push一个路由可以正常跳转,但是如果路由直接复制到浏览器或者刷新就会和后端的服务接口发生冲突,解决方法是对nginx进行url强制转化

  • vue.js Hash路由在微信授权跳转回来的时候由于带有#的原因,导致注入code和state会在#前面,无法用this.$route.query来获取,需要使用window.location.href自行正则匹配获取

  • Nuxt服务端渲染由于是第一次加使用后端直出的模版,但是当前端路由跳转后,就不是又后端直出了,而是使用前端的bundle文件进行渲染,在ios不会出现js-sdk invalid url的错误,但是在安卓下就会出错,而且js-sdk不能配置多次,一个页面进去只能配置一次,而且需要在wx.ready的回调方法里面对微信的js-sdk接口进行配置和使用。解决方法是不使用nuxt的vue-router,而是使用window的location.href来路由跳转,不过这样性能不好,最好是不要在router-view的外层进行配置,而是单独对里面的子页面进行配置

  • H5下,音频播放问题,在ios下面,播放音频视频都需要用户去触发事件才能执行

  • 正式配置微信的服务号,设置安全域名需要上传一个文件,类似证书吧,这个路径微信写着要放置到web的根目录下面,原来所谓的web根目录就是静态服务器下面的public文件夹,一开始配置nginx root路径好久,发现没卵用。

  • 原来微信下每个公众号的对应用户的openid都是不一样的,在发送消息推送,以为和测试号的id一样,最后查了数据库发现不一致

CSS写作建议和性能优化小结

css

CSS渲染规则

1
.nav h3 a { font-size: 14px; }
  • 渲染过程: 首先找到所有的a,沿着a的父元素查找h3,然后再沿着h3,查找.nav。中途找到了符合匹配规则的节点就加入结果集。如果找到根元素html都没有匹配,则不再遍历这条路径,从下一个a开始重复这个查找匹配(只要页面上有多个最右节点为a)

  • CSS选择器从右向左匹配:从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点);而从左向右的匹配规则的性能都浪费在了失败的查找上面

嵌套层级不要超过 3 级

  • CSS的元素嵌套层级不能超过3级,过度的嵌套会导致代码臃肿,冗余,导致CSS文件的体积变大,影响渲染的速度,而且过于依赖HTML文档结构。如果以后要修改样式,可能要使用!important覆盖。

样式级别

  • !important > 行内样式 > ID 样式 > Class 样式 > 标签名样式
  • 组合选择器使用权值会叠加的,ID 的权值是100,Class 是10,标签名是 1

css-weight

inline-block 的边距解决方法

  • 删除代码之前的空行空格
  • 父元素font-size设置为0

图片要设置 width 和 height

  • img很建议设置width和height。目的是为了在网速差或者其它原因加载不出图片的时候,保证布局不会乱

  • PC 站:建议在img标签的属性设置width和height。这样避免加载不出 CSS 而错位;

  • 手机站:建议用 CSS 设置img的width和height,因为手机站要做适配,在属性设置width和height不灵活,比如使用rem布局,在属性那里设置不了width和height;

  • 如果图片不固定:但是有一个max-width和max-height,那么建议在img的父元素设置width和height。img根据父元素的width和height设置max-width和max-height。

任意元素垂直居中

  • table-cell:

table-cell

  • flex (过于简单,忽略)

  • position, transform

pos-tran

  • position margin

pos-mar

这个方式不推荐使用,因为这个写法,.div2的宽高必须要设置,否则就是 100%,比如设置了top:0;bottom:0;效果和设置height:100%;是一样的。如果想要避免,就必须要设置height。

图片预加载

  • 懒加载:页面加载的时候,先加载一部分内容(一般是先加载首屏内容),其它内容等到需要加载的时候再进行加载。
  • 预加载:页面加载的时候,先加载一部分内容(一般是先加载首屏内容),其它内容等到先加载的一部分内容(一般是首屏内容)加载完了,再进行加载。

慎用 * 通配符

  • 通配符代码少,但是性能差,因为渲染的时候,要匹配页面上所有的元素

CSS 在 head 引入

  • 浏览器在所有的 stylesheets 加载完成之后,才会开始渲染整个页面。在此之前,浏览器不会渲染页面里的任何内容,页面会一直呈现空白。这也是为什么要把 stylesheet 放在头部的原因。如果放在 HTML 页面底部,页面渲染就不仅仅是在等待 stylesheet 的加载,还要等待 HTML 内容加载完成,这样一来,用户看到页面的时间会更晚。

避免使用 @import

  • @import会影响浏览器的并行下载,使得页面在加载时增加额外的延迟,增添了额外的往返耗时。而且多个@import可能会导致下载顺序紊乱。

不要在 ID 选择器前面进行嵌套或写标签

  • ID 在页面上本来就是唯一的,而且人家权值那么大,前方嵌套(.content #test)完全是浪费性能,以及多写一些没有意义的代码。这个虽然是一句话,但是还是有人犯这样的错。
  • 除了嵌套,在 ID 的前面也不需要加标签或者其它选择器。比如div#test或者.test#test。这两种方式完全是多余的,理由就是 ID 在页面就是唯一的。前面加任何东西都是多余的!

CSS3 动画的优化

  • CSS3 动画或者过渡尽量使用transform和opacity来实现动画,不要使用left和top;
  • 动画和过渡能用CSS3解决的,就不要使用JS。如果是复杂的动画可以使用CSS3+JS(或者HTML5+CSS3+JS)配合开发

初音未来也会跳极乐净土

bikini-miku

ThreeJS

三个必要元素: 场景(scene), 相机(camera), 渲染器(renderer)

场景相当于舞台,相机相当于眼睛,渲染器用来把相机拍到的呈现在渲染在浏览器上

  • 场景:场景提供了添加/删除物体的方法, 同时也涵盖添加雾效果以及材质覆盖等方法;

  • 相机:相机提供在浏览器下以不同的角度去观看场景,常用的相机类型为:CubeCamera(立方体), OrthographicCamera(正交),PerspectiveCamera(远景)

  • 渲染器:渲染器决定了渲染的结果应该画在页面的什么元素上面,并且以怎样的方式来绘制

详细概念

  • 相机:照相机分为两种, 使用透视投影照相机获得的结果是类似人眼在真实世界中看到的有“近大远小”的效果,而使用正交投影照相机获得的结果就像我们在数学几何学课上老师教我们画的效果,对于在三维空间内平行的线,投影到二维空间中也一定是平行的。然后创建6个透视投影相机并渲染到1个WebGL渲染器目标(WebGLRenderTarget)Cube对象上就形成了CubeCamera(立方体相机)

  • 坐标系:右手坐标,需要注意物体的坐标位置与相机之间的关系

  • 光源

环境光:经过多次反射而来的光称为环境光,无法确定其最初的方向。环境光是一种无处不在的光。环境光源放出的光线被认为来自任何方向。因此,当你仅为场景指定环境光时,所有的物体无论法向量如何,都将表现为同样的明暗程度。
点光源:由这种光源放出的光线来自同一点,且方向辐射自四面八方。例如蜡烛放出的光,萤火虫放出的光。
聚光灯:这种光源的光线从一个锥体中射出,在被照射的物体上产生聚光的效果。使用这种光源需要指定光的射出方向以及锥体的顶角。
平行光:也称方向光(Directional Light),是一组没有衰减的平行的光线,类似太阳光的效果

多种光源可以叠加进行使用,实现不同的视觉灯光效果,也可以通过控制光源来实现阴影效果。

  • 渲染器
    • CanvasRenderer
    • WebGLRenderer

      Canvas渲染器不使用WebGL来绘制场景,而使用相对较慢的Canvas 2D Context API
      WebGL渲染器使用WebGL来绘制您的场景,使用WebGL将能够利用GPU硬件加速从而提高渲染性能。

ThreeJS实现必要过程

  • 初始化相机

    1
    2
    3
    4
    5
    initCamera() {
    // 使用远景相机PerspectiveCamera
    this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000)
    this.camera.position.z = 30
    }
  • 初始化场景

    1
    2
    3
    4
    initScene() {
    this.scene = new THREE.Scene()
    this.scene.background = new THREE.Color(0x000000)
    }
  • 初始化光线

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    initLight() {
    // 使用环境光
    let ambient = new THREE.AmbientLight(0x666666)
    // 添加光源到场景中
    this.scene.add(ambient)
    // 使用平行光
    let directionalLight = new THREE.DirectionalLight(0x887766)
    directionalLight.position.set(-1, 1, 1).normalize()
    this.scene.add(directionalLight)
    }
  • 初始化渲染器

    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
    initRenderer() {
    // 插入DOM节点
    document.body.appendChild(this.container)
    // 使用WebGLRenderer
    this.renderer = new THREE.WebGLRenderer({
    antialias: true // 抗锯齿
    })
    // 设置像素
    this.renderer.setPixelRatio(window.devicePixelRatio)
    // 设置渲染面积
    this.renderer.setSize(window.innerWidth, window.innerHeight)
    // 渲染器renderer的domElement元素,表示渲染器中的画布,所有的渲染都是画在domElement上的
    this.container.appendChild(this.renderer.domElement)
    }
    ```
    - 添加一个物体
    ```javascript
    initObject() {
    // 几何物体,一个立方体
    let geometry = new THREE.CubeGeometry(1, 1, 1);
    // 物体材质
    let material = new THREE.MeshBasicMaterial({ color: 0x00ff60 });
    // 物体实例
    this.cube = new THREE.Mesh(geometry, material);
    this.scene.add(this.cube);
    }
  • 添加动画

    1
    2
    3
    4
    5
    animate () {
    this.cube.rotation.x += 0.1;
    this.cube.rotation.y += 0.1;
    this.renderer.render(this.scene, this.camera)
    }
  • 循环渲染

    1
    2
    3
    4
    5
    6
    render () {
    this.animate()
    // 利用requestAnimationFrame()实现动画递归
    循环
    requestAnimationFrame(this.render.bind(this))
    }

根据上面六个过程可以组合为一个通用的类来进行Three的简单使用

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
class BaseThree {
constructor() {
this.renderer = null
this.width = null
this.height = null
this.camera = null
this.scene = null
this.light = null
this.windowHalfX = window.innerWidth / 2
this.windowHalfY = window.innerHeight / 2
this.container = document.createElement('div')
}

initCamera() {}
initScene() {}
initLight() {}
initRenderer() {}
initObject() {}
render () {}
animate () {}

start () {
// 初始化
this.initCamera()
this.initScene()
this.initLight()
this.initRenderer()
this.initObject()
// 开始渲染
this.render()
}
}

WebVR

WebVR简单了解

  • WebVR的体验方式
    • VR模式
      • 滑配式HMD + 移动端浏览器:浏览器水平陀螺仪的参数来获取用户的头部倾斜和转动的朝向,并告知页面需要渲染哪一个朝向的场景,移动端需使用Chrome Beta版本的浏览器
      • 分离式HMD + PC端浏览器:佩戴Oculus Rift的分离式头显浏览连接在PC主机端的网页,没有体验过。。。
    • 裸眼模式: 使用鼠标拖拽场景

实现WebVR准备工作

需要引入的js插件:

  1. webvr-polyfill.js:需要引入webvr-polyfill.js来支持WebVR网页,它提供了大量VR相关的API,比如Navigator.getVRDevices()获取VR头显信息的方法
  2. VRcontrols.js:VR控制器,是three.js的一个相机控制器对象,引入VRcontrols.js可以根据用户在空间的朝向渲染场景,它通过调用WebVR API的orientation值控制camera的rotation属性
  3. VReffect.js:VR分屏器,这是three.js的一个场景分屏的渲染器,提供戴上VR头显的显示方式,VREffect.js重新创建了左右两个相机,对场景做二次渲染,产生双屏效果。
  4. webvr-manager.js:这是WebVR的方案适配插件,它提供PC端和移动端的两种适配方式,通过new WebVRManager()可以生成一个VR图标,提供VR模式和裸眼模式的不同体验,当用户在移动端点击按钮进入VR模式时,WebVRManager便会调用VREffect分屏器进行分屏,而退出VR模式时,WebVRManager便用回renderer渲染器进行单屏渲染。

初始化VR控件

1
2
3
4
5
6
initVR() {

this.effect = new THREE.VREffect(this.renderer)
this.controls = new THREE.VRControls(this.camera)
this.manager = new WebVRManager(this.renderer, this.effect)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 动画渲染更新
render() {
if (this.ready) {
this.helper.animate(this.clock.getDelta())
if (this.physicsHelper != null && this.physicsHelper.visible) {
// 物理重力等效果
this.physicsHelper.update()
}
// 加载MMD模型时使用
if (this.ikHelper != null && this.ikHelper.visible) {
this.ikHelper.update()
}
// 使控制器能够实时更新
this.controls.update()
// this.renderer.render(this.scene, this.camera)
this.manager.render(this.scene, this.camera)
}
}

WebVR结合ThreeJS发生的碰撞

导入MMD模型

  • 引入MMD模型解析脚本
    • MMDParser.js
    • TGALoader.js
    • MMDLoader.js
    • CCDIKSolver.js
    • MMDPhysics.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
53
54
55
56
57
// 导入mmd模型
importMMD() {
// 模型文件路径 ^_^可以引入各种性感的初音未来模型
let modelFile = 'asserts/models/mmd/bikini-miku/sakura.pmx'
// 模型动作文件, 用了极乐净土的模型动作
let vmdFiles = ['asserts/models/mmd/vmds/power.vmd']
// 背景音乐文件, 背景音乐当然是极乐净土啦
let audioFile = 'asserts/models/mmd/audios/power.mp3'
let audioParams = { delayTime: 0 }
// 新建一个loader用于加载mmd模型
let loader = new THREE.MMDLoader()
this.helper = new THREE.MMDHelper()
// 异步加载文件
loader.load(modelFile, vmdFiles, object => {
this.mesh = object
this.mesh.position.y = -10
// this.scene.add(this.mesh)
this.helper.add(this.mesh)
// 设置模型动画
this.helper.setAnimation(this.mesh)
this.ikHelper = new THREE.CCDIKHelper(this.mesh)
this.ikHelper.visible = false
this.scene.add(this.ikHelper)
// 设置模型物理动效
this.helper.setPhysics(this.mesh)
this.physicsHelper = new THREE.MMDPhysicsHelper(this.mesh)
this.physicsHelper.visible = false
this.scene.add(this.physicsHelper)
this.helper.doAnimation = true
this.helper.doIk = true
this.helper.enablePhysics = true
// 加载音乐
loader.loadAudio(audioFile, (audio, listener) => {
listener.position.z = 1
this.helper.setAudio(audio, listener, audioParams)
this.helper.unifyAnimationDuration({ afterglow: 2.0 })
this.scene.add(audio)
this.scene.add(listener)
this.scene.add(this.mesh)
this.ready = true
}, xhr => {
if (xhr.lengthComputable) {
var percentComplete = xhr.loaded / xhr.total * 100
console.log(Math.round(percentComplete, 2) + '% downloaded')
}
}, err => {
console.log(err)
})
}, xhr => {
if (xhr.lengthComputable) {
var percentComplete = xhr.loaded / xhr.total * 100
console.log(Math.round(percentComplete, 2) + '% downloaded')
}
}, err => {
console.log(err)
})
}

创建一个全景环境

  • 准备文件:6张不同视角的图片,或者一张全景拍摄的图片

  • 读取图片文件并渲染

  • 使用6张不同视角图片组合的立体全景效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let urls = [
r + "px.jpg", r + "nx.jpg",
r + "py.jpg", r + "ny.jpg",
r + "pz.jpg", r + "nz.jpg"
]
// 加载图片
let textureCube = new THREE.CubeTextureLoader().load(urls)
textureCube.format = THREE.RGBFormat
textureCube.mapping = THREE.CubeReflectionMapping
let cubeShader = THREE.ShaderLib["cube"]
let cubeMaterial = new THREE.ShaderMaterial({
fragmentShader: cubeShader.fragmentShader,
vertexShader: cubeShader.vertexShader,
uniforms: cubeShader.uniforms,
depthWrite: false,
side: THREE.BackSide
})
cubeMaterial.uniforms["tCube"].value = textureCube
let cubeMesh = new THREE.Mesh(new THREE.BoxBufferGeometry(100, 100, 100), cubeMaterial)
this.scene.add(cubeMesh)

由于只是为了体验,没有关注性能优化,请见谅。
github源码

Node环境开发Opencv

Opencv 安装使用

1
2
3
4
mkdir release
cd release
cmake -G "Unix Makefiles" .. (为Unix 系统生成Makefile,Mac OSX是基于Unix的。未安装cmake 可以通过Homebrew安装,未安装Homebrew需安装Homebrew)
make
  • 安装Opencv
1
make install
  • 安装完成的目录
1
2
3
/usr/local/lib (Opencv库文件)
/usr/local/include (Opencv头文件)
/usr/local/share/ (Opencv xml配置文件)
  • 使用Opencv(C++ Version)

编写CMakeLists.txt文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
project( ORBFeatureAndCompare ) // 项目的名称
cmake_minimum_required(VERSION 2.8) // cmake的版本要求
find_package( OpenCV REQUIRED ) // 查找对应的Opencv依赖库
find_package(Boost COMPONENTS log log_setup thread filesystem system) // 查找对应的Boost依赖库(下文出现Boost的安装方法)
add_executable( ORBFeatureAndCompare ORBFeatureAndCompare ) // 指定可运行的文件
// 引入对应的依赖库文件的位置
target_link_libraries(ORBFeatureAndCompare
${OpenCV_LIBS}
${Boost_LOG_SETUP_LIBRARY}
${Boost_LOG_LIBRARY}
${Boost_FILESYSTEM_LIBRARY}
${Boost_THREAD_LIBRARY}
${Boost_SYSTEM_LIBRARY}
)

编写Opencv的cpp文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//必要的头文件
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <iostream>
#include <fstream>
#include <cstring>
#include <iterator>
#include <vector>

using namespace boost::filesystem;
namespace newfs = boost::filesystem;
using namespace cv;
using namespace std;

int main () {
....
return 0;
}

创建一个images(其他命名都可以)的资源文件夹

1
2
3
4
cd images
cmake ..
make
./ORBFeatureAndCompare //编译出的可执行程序

安装使用Boost进行数据序列化

1
2
./bootstrap.sh
./b2
  • 引入Boost库进行数据序列化,同样是在CmakeList中引入文件路径, 在cpp文件中使用

封装cpp程序为Node Addon

  • 依赖安装

    • 全局安装node-gyp
    • 本地安装nan
  • 编写binding.gyp

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
{
"targets": [
{
"target_name": "feature",
"sources": [ "./src/feature.cc" ],
"include_dirs": [
"<!(node -e \"require('nan')\")",
"/usr/local/include/boost",
"/usr/local/include/opencv2"
],
"conditions": [
[ "OS==\"linux\" or OS==\"freebsd\" or OS==\"openbsd\" or OS==\"solaris\" or OS==\"aix\"", {
}
],
["OS==\"mac\"", {
"libraries": [
"/usr/local/lib/libboost_log_setup-mt.dylib",
"/usr/local/lib/libboost_log-mt.dylib",
"/usr/local/lib/libboost_filesystem-mt.dylib",
"/usr/local/lib/libboost_thread-mt.dylib",
"/usr/local/lib/libboost_system-mt.dylib",
"/usr/local/lib/libopencv_core.3.2.0.dylib",
"/usr/local/lib/libopencv_highgui.3.2.0.dylib",
"/usr/local/lib/libopencv_imgproc.3.2.0.dylib",
"/usr/local/lib/libopencv_features2d.3.2.0.dylib"
]
}
]
]
}
]
}
  • 编写node c++ 插件
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <node.h>
#include <nan.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <iostream>
#include <fstream>
#include <string>
#include <iterator>
#include <vector>

using namespace boost::filesystem;
namespace newfs = boost::filesystem;
using namespace v8;
using namespace std;

vector<uchar> matToString(cv::Mat descriptorMat) {
vector<uchar> buf;
imencode(".png", descriptorMat, buf);
return buf;
}

vector<uchar> descriptorMat(cv::Mat image) {
vector<cv::KeyPoint> keyPoint;
cv::Ptr<cv::ORB> orb = cv::ORB::create(4000, 1.2f, 8, 31, 0, 2, cv::ORB::HARRIS_SCORE, 31, 20);
orb->detect(image, keyPoint);
cv::Mat descriptorMat;
orb->compute(image, keyPoint, descriptorMat);
return matToString(descriptorMat);
}

void imageFeature(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.Length() < 1) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments")));
return;
}
if (!args[0]->IsString()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong arguments")));
return;
}
String::Utf8Value pathValue(Local<String>::Cast(args[0]));
string path = string(*pathValue);
cv::Mat image = cv::imread(path, 1);
if (image.empty()) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Input image error")));
return;
}
vector<uchar> descriptorString = descriptorMat(image);
Local<Object> buf = Nan::NewBuffer(descriptorString.size()).ToLocalChecked();
uchar* data = (uchar*) node::Buffer::Data(buf);
memcpy(data, &descriptorString[0], descriptorString.size());
v8::Local<v8::Object> globalObj = Nan::GetCurrentContext()->Global();
v8::Local<v8::Function> bufferConstructor = v8::Local<v8::Function>::Cast(globalObj->Get(Nan::New<String>("Buffer").ToLocalChecked()));
v8::Local<v8::Value> constructorArgs[3] = {buf, Nan::New<v8::Integer>((unsigned)descriptorString.size()), Nan::New<v8::Integer>(0)};
v8::Local<v8::Object> actualBuffer = bufferConstructor->NewInstance(3, constructorArgs);
args.GetReturnValue().Set(actualBuffer);
}

int bfMatcherCompare (cv::Mat &descriptors1, cv::Mat &descriptors2) {
cv::BFMatcher matcher(cv::NORM_HAMMING);
vector<cv::DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
double max_dist = 0;
double min_dist = 100;
/*
for (int i = 0; i < descriptors1.rows; i++)
{
double dist = matches[i].distance;
if (dist < min_dist)
min_dist = dist;
if (dist > max_dist)
max_dist = dist;
}
vector<cv::DMatch> good_matches;
for (int i = 0; i < descriptors1.rows; i++)
{
if (matches[i].distance < 3 * min_dist)
good_matches.push_back(matches[i]);
}
return good_matches.size();
*/
for (int i = 0; i < descriptors1.rows; i++) {
double dist = matches[i].distance;
if (dist < min_dist) {
min_dist = dist;
}
if (dist > max_dist) {
max_dist = dist;
}
}
std::vector<cv::DMatch> good_matches;
double good_matches_sum = 0.0;

for (int i = 0; i < descriptors1.rows; i++) {
double distance = matches[i].distance;
if (distance <= std::max(2 * min_dist, 0.02)) {
good_matches.push_back(matches[i]);
good_matches_sum += distance;
}
}
return (double) good_matches_sum / (double) good_matches.size();
}

void similarity(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<Object> arg1 = args[0]->ToObject();
int size1 = args[1]->NumberValue();
Local<Object> arg2 = args[2]->ToObject();
int size2 = args[3]->NumberValue();
if (args.Length() < 4) {
isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Wrong number of arguments")));
return;
}
uchar*buffer1 = (uchar*) node::Buffer::Data(arg1);
std::vector<uchar> vec1(buffer1, buffer1 + (unsigned int) size1);
cv::Mat img_decode1;
img_decode1 = cv::imdecode(vec1, CV_8U);
uchar*buffer2 = (uchar*) node::Buffer::Data(arg2);
std::vector<uchar> vec2(buffer2, buffer2 + (unsigned int) size2);
cv::Mat img_decode2;
img_decode2 = cv::imdecode(vec2, CV_8U);
int similarity = bfMatcherCompare(img_decode1, img_decode2);
args.GetReturnValue().Set(similarity);
}

void init(Local<Object> exports, Local<Object> module) {
NODE_SET_METHOD(exports, "imageFeature", imageFeature);
NODE_SET_METHOD(exports, "similarity", similarity);
}

NODE_MODULE(addon, init)
  • 编写Js文件
1
2
3
4
5
6
7
8
9
10
11
const feature = require('./build/Release/feature');

exports.getImageFeature = (filePath) => {
return feature.imageFeature(filePath).toString('utf8');
};

exports.getImageSimilarity = (descriptor1, descriptor2) => {
let matBuffer1 = Buffer.from(descriptor1);
let matBuffer2 = Buffer.from(descriptor2);
return feature.similarity(matBuffer1, matBuffer1.length, matBuffer2, matBuffer2.length);
};
  • 编译运行
1
2
node-gyp configure build
node test.js

C++语法解析器

架构分析

  1. Node进行文件数据处理
  2. Vue.js进行视图的呈现
  3. Electron作为客户端

C++语法解析

效果展示

cppAnalysis

读取形式

按照代码行进行读取, 每次读取到一行就进行遍历分析

关键字与特殊符号建立映射

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
/**
* 关键字
*/
let keyWords = [
'asm', 'auto',
'bool', 'break',
'case', 'catch', 'char', 'class', 'const', 'continue', 'cin', 'cout',
'default', 'delete', 'do', 'double', 'define',
'else', 'enum', 'except', 'explicit', 'extern', 'endl',
'false', 'finally', 'float', 'for', 'friend',
'goto',
'if', 'inline', 'int', 'include',
'long',
'mutable', 'main',
'namespace', 'new',
'operator',
'private', 'protectde', 'public', 'printf',
'register', 'return',
'short', 'signed', 'szieof', 'static', 'struct', 'string', 'switch', 'std', 'scanf',
'template', 'this', 'throw', 'true', 'try', 'typedef', 'typename',
'union', 'unsigned', 'using',
'virtual', 'void',
'while'
]

/**
* 特殊字符
*/
let specialWords = [
',', ';', '(', ')', '{', '}', '#', '^', '?', ':', '.', '[', ']', '+', '-', '*', '/', '%', '=', '>', '<', '!', '~', '|', '&',
'&&', '||', '==', '>=', '<=', '!=', '++', '--', '::', '<<', '>>', '+=', '-=', '*=', '/=', '%=', '&=', '^=', '->'
]

keyWords.forEach(word => {
wordsMap.set(word, 'keyWord')
})

specialWords.forEach(word => {
wordsMap.set(word, 'specialWord')
})

遍历分析过程

  • 代码注释匹配

    • 当读到一行中包含\/的两个字符时候, 这时把代码注释的标志设置为true, 然后一直读取,知道遇到\/的时候就把标志重新置为false
    • 判断是否可以构成单词,成立的条件是不以数字开头,并且只包含数字, 下划线, 以及字母, 可以通过正则来/[a-z]|[A-z]|_/匹配
    • 判断是否为字符串或者字符, 成立条件是以”,’开头
    • 判断是否为数字
    • 判断是否为特殊字符, 这时就通过建立的映射进行关键字查找
    • 判断空格
  • 代码解释

判断工具函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

//判断是否是字母与下划线
function judgeWord(word) {
let wordPatten = /[a-z]|[A-z]|\_/
return wordPatten.test(word)
}

//判断是否为数字
function judgeNumber(number) {
let numberPatten = /[0-9]/
return numberPatten.test(number)
}

//判断是否为特殊字符
function judgeSpecialWord(letter) {
return wordsMap.get(letter) === 'specialWord' ? true : false
}

//判断是否为关键词
function judgeKeyWord(letter) {
return wordsMap.get(letter) === 'keyWord' ? true : false
}

行分析函数

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
exports.analysisLine = (line, annotation) => {
let oneLine = []
let word = ''
let i = 0
let includeFile = false
while (i < line.length) {
//注释代码的优先级最高,需要先进行判断
if (line[i] === '/' && line[i + 1] === '*') {
word += line[i]
i++
annotation = true;
}
if (!annotation) {
//表示不是注释内容
if (judgeWord(line[i])) {
//表示是属于字母与下划线
word += line[i]
while (i + 1 < line.length && (judgeNumber(line[i + 1]) || judgeWord(line[i + 1]))) {
i++
word += line[i]
}
if (judgeKeyWord(word)) {
//判断单词是否属于关键词
oneLine.push({
word: word,
type: 'keyWord'
})
} else {
//不为关键词, 则为标识符
oneLine.push({
word: word,
type: 'identifier'
})
}
//由于include是属于头文件的引入, 需要对头文件进行特殊处理
if (word === 'include') {
includeFile = true
}
word = ''
i++
} else if (line[i] === '"') {
//字符串判断
while (i + 1 < line.length && line[i + 1] !== '"') {
word += line[i]
i++
}
word += (line[i] || '' )+ (line[i + 1] || '')
oneLine.push({
word: word,
type: 'string'
})
word = ''
i += 2
} else if (line[i] === '\'') {
//字符判断
while (i + 1 < line.length && line[i + 1] !== '\'') {
word += line[i]
i++
}
word += (line[i] || '' )+ (line[i + 1] || '')
oneLine.push({
word: word,
type: 'char'
})
word = ''
i += 2
} else if (judgeNumber(line[i])) {
//数字判断
word += line[i]
while (i + 1 < line.length && (judgeNumber(line[i + 1]) || line[i + 1] === '.')) {
i++
word += line[i]
}
oneLine.push({
word: word,
type: 'number'
})
word = ''
i++
} else if (judgeSpecialWord(line[i])) {
//特殊字符判断
if (line[i] === '<' && includeFile) {
//处理头文件的引入
oneLine.push({
word: line[i],
type: 'specialWord'
})
i++
word += line[i]
while (i + 1 < line.length && line[i + 1] !== '>') {
i++
word += line[i]
}
oneLine.push({
word: word,
type: 'libraryFile'
})
word = ''
i++
} else {
//处理//的注释代码
if (line[i] === '/' && line[i + 1] === '/') {
i++
while (i + 1 < line.length) {
i++
word += line[i]
}
oneLine.push({
word: word,
type: 'Annotations'
})
word = ''
i += 3
} else {
word += line[i]
while (i + 1 < line.length && (judgeSpecialWord(word + line[i + 1]))) {
i++
word += line[i]
}
oneLine.push({
word: word,
type: 'specialWord'
})
word = ''
i++
}
}
} else if (line[i] === ' ') {
oneLine.push({
word: line[i],
type: 'space'
})
i++
}
} else {
//表示注释内容
while (i + 1 < line.length && (line[i + 1] !== '*' && line[i + 2] !== '/')) {
word += line[i]
i++
}
word += line[i] + (line[i + 1] || '') + (line[i + 2] || '')
oneLine.push({
word: word,
type: 'Annotations'
})
annotation = false;
word = ''
i += 3
}
}
return oneLine
}

界面实现过程

  • Electron, Vue搭建

具体的搭建过程省略, 可查看源码或自行查找资料

  • 动态编辑显示

    • 数据绑定, 使用v-model结合watch进行数据监控, 当数据发生变化的时候重新分析每行数据
    • 二维数组保存分析的数据, 使用二维数组保存分析完的词法, 保留原生的代码行
    • 通过v-bind:class对不同类型的词法进行代码高亮的简单呈现
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
<template>
<div class="container">
<div class="navigator">
<button class="menu-btn" @click="open">open</button>
<button class="menu-btn" @click="save">save</button>
</div>
<div class="content">
<div class="code-input">
<textarea v-model="codeInput">
</textarea>
</div>
<div class="code-show">
<div v-for="(line, index) in codeShowArr">
<p v-for="word in line" :class="word.type">
<template v-if="word.type==='space'">
&nbsp
</template>
<template v-else>
{{word.word}}
</template>
</p>
</div>
</div>
</div>
</div>
</template>

<script>
import { analysisLine } from '../controllers/analysis'
import fileController from '../controllers/fileController'

export default {
watch: {
codeInput: function() {
this.codeShowArr = []
this.codeInput.split(/\r?\n/).forEach(line => {
let wordArr = []
analysisLine(line, this.annotation).forEach(word => {
wordArr.push(word)
})
this.codeShowArr.push(wordArr)
})
}
},
data () {
return {
codeInput: '',
codeShow: '',
codeShowArr: [],
annotation: false
}
},
methods: {
open() {
fileController.openDialog().then(filePath => {
return fileController.readPackage(filePath)
}).then(res => {
this.codeInput = res
})
},
save() {
fileController.openDialog().then(filePath => {
return fileController.writePackage(filePath, this.codeShowArr)
})
}
}
}
</script>
  • 文件的读取和写入

    • 使用Electron中引入Node的fs模块来进行文件读取写入处理
    • 使用Electron的Dialog控件选取文件
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

import Promise from 'bluebird'
import electron from 'electron'
const {dialog} = electron.remote
const fs = Promise.promisifyAll(require('fs'));

const writePackage = (filePath, data) => {
return fs.writeFileAsync(filePath, JSON.stringify(data, null, 2)).catch(err => {
return Promise.reject(err)
});
}

const readPackage = (filePath) => {
return fs.readFileAsync(filePath, 'utf-8').catch(err => {
return Promise.reject(err)
});
}

const openDialog = () => {
return new Promise((resolve, reject) => {
dialog.showOpenDialog({
properties: [
'openFile',
]
}, (res) => {
if(!res) {
return reject('404')
}
return resolve(res[0])
});
})
}

export default {
writePackage,
readPackage,
openDialog
}

对此, 一个简单的C++词法分析就完成了, 过程并不复杂, 只要理解了优先级和并列情况的区分就比较清晰的写出, 由于
个人匆匆写完这个程序, 并没有对js部分的代码进行优化, 质量过低, 仅供参考

MongoDB原生命令CURD

《MongoDB权威指南》学习摘录

Insert

使用insert方法

  • 该操作文档会自动生成一个”_id”键
1
>db.foo.insert({“bar” : “baz”})

批量插入,使用batchInsert函数

1
>db.foo.batchInsert([{ "_id" : 0 } , { "_id" : 1 } , { "_id" : 2 }])

当前mongodb能接受的最大消息长度是48MB,并且如果在执行批量插入过程中有一个文档插入失败,则在该文档之前的所有文档都会成功插入到集合中,而这个文档之后的所有文档全部插入失败。

Remove

使用remove方法

1
2
3
4
5
//删除foo集合的所有文档
>db.foo.remove()

//删除指定查询文档作为可选参数
>db.mailing.list.remove( { “opt-out” : true } )

使用drop()比直接删除集合会更快

1
>db.foo.drop()

Update

文档替换

1
2
3
4
5
6
{
"_id": ObjectId("..."),
"name": "joe",
"friends": 32,
"enemies": 2
}

现在需要更新”friends”和”enemies”两个字段到”relationships”子文档中

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

>var joe = db.users.findOne({"name":joe});
>joe.relationships = {"friends":joe.friends,"enemies":joe.enemies};
{
"friends":32,
"enemies":2
}
>joe.username = joe.name;
"joe"
>delete joe.friends;
true
>delete joe.enemies;
true
>delete joe.name;
true
>db.users.update({"name":"joe"},joe);

更新后的文档

1
2
3
4
5
6
7
8
{
"_id":ObjectId("..."),
"username":"joe",
"relationships":{
"friends":32,
"enemies":2
}
}

常见的错误是查询条件匹配到多个文档,然后更新的时候回由于第二个参数的存在就产生重复的”_id”值,对于这种情况要使用”_id”值来进行查询

使用修改器

  • “$inc”修改器使用

当有人访问页面的时候,就通过url找到该页面,然后使用”$inc”修改器增加”pagerviews”的值

1
2
3
4
5
{
"_id":ObjectId("..."),
"url":"www.lgybetter.com",
"pagerviews":52
}
1
>db.analytics.update({"url":"www.lgybetter.com"},{"$inc" : {"pagerviews" : 1}})

注意,使用修改器时,”_id”并不会改变,而文档替换过则会

  • “$set”修改器使用
1
>db.user.update({"_id":ObjectId(...)},{"$set" : {"favorite book" : "War and Peace"}})

于是更新后的文档就有了”favorite book”键,如果要继续修改则:

1
>db.user.update({"_id":ObjectId(...)},{"$set" : {"favorite book" : "Green Eggs and Ham"}})

也可以变为数组的类型

  • 使用”$unset”可以将这个键完全删除:
1
>db.user.update({"name":joe},{"$unset" : {"favorite book" : 1}})
  • “$push”修改器使用

如果数组存在,”$push”会向已有的数组的末尾加入一个元素,如果没有改数组就创建一个新的数组

1
2
3
4
5
{
"_id":ObjectId("..."),
"title":"A blog post",
"content":"..."
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>db.blogs.posts.update({"title" : "A blog post"},{"$push" : {
"comments" : {"name" : "joe","email" : "joe@example.com","content":"nice post."}
}})
>db.blog.posts.findOne()
{
"_id":ObjectId("..."),
"title":"A blog post",
"content":"...",
"comments": [
{
"name":"joe",
"email":"joe@example.com",
"content":"nice post"
}
]
}

注意,”$push”会创建一个数组的类型,可用于动态添加数据

  • 用”$push”一次添加多个数据,配合使用”$each”
1
2
3
4
5
>db.stock.ticker.update({"_id":"GOOG"},{
"$push" : {"hourly" : { "$each" : [
562.776,562.790,559.123
]}}
})
  • 使用”$slice”来固定数组的最大长度
1
2
3
4
5
6
7
8
>db.movies.find({"genre" : "horror"},{
"$push" : { "top10" : {
"$each" : ["Nightmare on Elm Street" , "Saw"],
"$slice" : -10,
"$sort" : {"rating" : -1}
}
}
})

注意,”$slice”的值应该是负整数,”$sort”可以用来对数组中的所有对象进行排序,不能只将”$slice”或者”$sort”与”$push”配合使用,且必须使用”$each”。

  • 使用”$ne”,使得数组变为数据集合
1
2
3
>db.paper.update({"authors cited" : {"$ne" : "Richie"},
{"$push" : {"authors cited" : "Richie"}}
})
  • “$addToSet”和”$each”组合使用,效果是与”$ne”一样,但是可以用来同时插入多条数据
1
2
3
4
5
>db.users.update({"_id" : ObjectId("...")}, {"$addToSet" :
{"emails" : {"$each" :
["joe@php.net","joe@example.com","joe@python.org"]
}}
})
  • 使用”$pop”删除数组元素{“$pop” : {“key” : 1}}从数组末尾删除,{“$pop” : {“key” : -1}}从数组头部删除

  • 使用”$pull”删除元素

1
>db.lists.update({},{"$pull" : {"todo" : "laundry"}})

使用upsert

upsert是一种特殊的更新,要是没有找到符合更新条件的文档,就会以这个条件和更新
文档为基础创建一个新的文档。

1
>db.analytics.update({"url" : "/blog"} , {"$inc" : {"pageviews" : 1}} ,true)
  • 有时候,需要在创建文档的同时创建字段并为它赋值,但是在之后的所有更新操作中,该字段的值就不再改变。这时候就使用”$setOnInsert”
1
>db.users.update({} , {"$setOnInsert" : {"createdAt" new Date()}},true)

更新多个文档

具体的操作值通过:

1
>db.runCommand({getLastError:1})

返回结果

1
2
3
4
5
6
{
"err":null,
"updatedExisting":true,
"n":5,
"ok":true
}

Find

find入门操作

  • 查询该集合下的所有文档

    1
    >db.c.find()
  • 查询特定值的文档,可以同时添加多个条件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >db.users.find({"age" : 20})
    >db.users.find({"age" : 20 , "name" : "lgy"})
    //指定需要返回的键
    >db.users.find({} , {"username" : 1,"email" : 1})
    //返回结果
    {
    "_id":ObjectId("..."),
    "username":"lgy",
    "email":"lgy@example.com"
    }
  • 通过查询,默认情况下回返回”_id”这个键,如果希望不出现其他的键值,就使用如下:

    1
    2
    3
    4
    5
    >db.users.find({},{"username" : 1,"_id" : 0})
    //返回结果
    {
    "username":"lgy"
    }

查询条件

  • 查询条件:

“$lt”,”$lte”,”$gt”,”$gte”就是全部的比较操作符,分别对应<,<=,>,>=

1
2
//查询18 ~ 30岁的用户:
>db.users.find({"age" : {"$gte" : 18 , "$lte" : 30}})

“$ne”表示不等于某个特定的值

1
2
//查询用户名不为"lgy"的所有用户
>db.users.find({"username" : {"$ne" : "lgy"}})

  • OR查询:

“$in” 和 “$or”两种方式进行OR查询

1
>db.raffle.find({"ticket_no" : {"$in" : [725,542,390]}})

“$nin”是与”$in”相反的

1
>db.raffle.find({"$or" : [{"ticket_no" : 725},{"winner" : true}]})
  • 两者结合使用:
    1
    >db.raffle.find({"$or" : [{"ticket_no" : {"$in" : [725,542,300]}},{"winner" : true}]})

$not

“$not”是元条件句,即可以在任何其他条件之上。

"$mod"通过传入两个参数,第一个用来作为除数,第二个是用来判定余数是否为此数字
1
>db.user.find({"id_num" : {"$not" : {"$mod" : [5,1]}}})

特定类型的查询

  • null

null不仅会匹配某个键的值为null的文档,而且还匹配不包含这个键的文档

1
>db.c.find({"z" : null})

如果仅仅想匹配键值为null的文档,则可以加”$exists”条件:

1
>db.c.find({"z" : {"$in" : [null], "$exists" : true}})
  • 正则表达式
1
>db.users.find({"name" : /joey?/i})
  • 查询数组

$all,可以用来进行多个元素匹配数组

1
2
//这样查找就可以匹配到同时存在的两个元素的文档
>db.food.find({"fruit" : {"$all" : ["people","banana"]}})

$size用来查询特定长度的数组

1
>db.food.find({"furit" : {"$size" : 3}})

$slice用来返回某个匹配数组元素的一个子集

1
2
3
4
5
//返回前十条评论,如果把10换成-10就是返回后十条评论
>db.blog.posts.findOne(criteria,{"comments" : {"$slice" : 10}})

//这个操作会跳过前面23个元素,返回第24~33个元素
>db.blog.posts.findOne(criteria,{"comments" : {"$slice" : [23,10]}})

返回一个匹配的数组元素

1
2
3
4
5
6
7
8
9
10
11
12
13
>db.blog.find({"comments.name" : "bob"}, {"comments.$" : 1})
//返回结果
{
"id" : ObjectId("..."),
"comments" : [
{
"name" : "bob",
"email" : "bob@example.com",
"content" : "good post"
}
]
}
//这样就只会返回第一条评论
  • 查询内嵌文档

现在有这样的数据

1
2
3
4
5
6
7
{
"name": {
"first":"joe",
"last":"Schmoe"
},
"age":45
}

对内嵌文档的查询

1
2
//用点表示法表达"进入内嵌文档内部"的意思
>db.people.find({"name.first" : "joe" ,"name.last" : "Schmoe"})

要正确指定一组条件,而不必指定每个键,就需要使用”$elematch”

1
>db.blog.find({"comments" : {$elematch" : {"author" : "joe", "score" : {"$gte" : 5}}}})

游标

  • 游标查询的具体操作:
1
2
3
>for(i = 0; i <100; i ++) {
db.collection.insert({x : i});
}

计算机网络体系结构

一. 层次总览

1. TCP/IP 体系结构

  • 应用层: TELNET, FTP, SMTP

  • 运输层: TCP或UDP

  • 网际层: IP

  • 网络接口层

2. 五层协议体系结构(综合OSI和TCP/IP的优点)

  • 应用层: 通过应用进程间的交互来完成特定网络应用

  • 运输层: 两个主机进程之间的通信提供通用的数据传输服务

  • 网络层: 为分组交换网上的不同主机提供通信服务。另一个任务就是要选择合适的路由,使源主机传输的分组能够找到目的主机

  • 数据链路层: 两个相邻结点之间传送数据,根据链路层将网络层交付的IP数据报文封装成帧。

  • 物理层: 传输比特数据

二. 层次分析

1. 数据链路层

点对点信道: 一对一的点对点通信

广播信道: 一对多的广播信道通信

  • 同一个局域网,分组怎样从一个主机传送到另一个主机(不经过路由器),这是属于数据链路层的范围

  • 数据链路层的三个基本问题: 封装成帧, 透明传输, 差错监测

  • 封装成帧: 给一段数据添加首尾部分,通过帧定界符(SOH/EOT)来判定,为提高帧的传输效率,应使得帧的数据部分长度尽可能大于首尾部分的长度(通过MTU,最大传送单元来控制)

  • 透明传输: 简单地说就是不管从键盘上输入什么字符都可以放在帧中进行传输,其中需要注意的是当数据和帧定界符冲突时要对帧定界符进行转义

  • 差错监测: 循环冗余监测CRC

  • 点对点PPP协议: 1. 简单 2. 封装成帧 3. 透明性 4. 多种网络层协议 5. 多种类型链路 6. 差错监测 7. 检测链接状态 8. 传送最大单元 9. 网络层地址协商 10. 数据压缩协商

  • 发送帧的类型: 1. 单播(一对一) 2. 广播(一对全体,全1地址) 3. 多播(一对多)

  • mac帧的格式: 目的地址 + 源地址 + 类型(标明交付上一层对应的协议) + 数据(IP数据报)

2. 网络层

网络层特点: 向上提供简单灵活的,无连接的,尽最大努力交付的数据报服务

  • IP协议

    • IP地址就是给因特网上的每一个主机(或路由器)的每一个接口分配一个世界唯一的32位标识符

    • IP地址 :: = {<网络号>, <主机号>}

  • 地址解析协议ARP

    • 根据IP地址获取MAC地址

    • 每一个主机都有一个ARP高速缓存表

  • 网际控制报文协议ICMP

    • ICMP差错报告报文: 1. 终点不可达(无法到达目的主机) 2. 源点抑制(目的主机由于拥塞丢失数据报) 3. 时间超过(主机收到部分数据报,但是其他的数据报接收超过限定时间,发送时间超过报文) 4. 参数问题(报文中有参数不正确) 5. 改变路由(重定向)

3. 运输层

运输层: 向它上面的应用层提供通信服务, 真正进行通信的实体是在主机中的进程(一个主机的进程和另一个主机的一个进程在交换数据)

  • UDP在传送数据之前不需要建立连接,不需要提供可靠交付

  • TCP提供面向连接的服务·

  • UDP协议

    • UDP是无连接的,使用尽最大努力交付,面向报文,没有拥塞控制

    • UDP的首部格式: 源端口 + 目的端口 + 长度 + 校验和

    • 不使用拥塞控制的UDP有可能会引起网络产生严重的拥塞问题

  • TCP协议

    • TCP是面向连接的运输层协议,每一条TCP链接只能

设计模式之面向对象六大原则

1.单一职责原则

  • 单一职责原则是:对一个类而言,应该仅有一个引起它变化的原因。也就是,一个类中应该是一组相关性很高的函数,数据的封装。

2.开闭原则

  • 开闭原则是:软件中的对象(类,模块,函数等)应该对于扩展是开放的,但是,对于修改时封闭的。程序一旦完成,程序中的一个类的实现只是因为错误而被修改,新的或者改变的特性应该通过新建不同的类实现,新建的类可以通过继承的方式来重用原类的代码。

3.里氏替换原则

  • 里氏替换原则是:如果对每个类型为S的对象O1,都有类型为T的对象O2,使得以T定义的所有程序P在所有的对象O1都代换成O2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。简单来说就是,所有引用基类的地方必须能透明地使用其子类的对象,就是父类能出现的地方子类就可以出现,替换成子类时不会发生错误,反过来就不行。

4.依赖倒置原则

  • 依赖倒置原则指代了一种特定的解耦形式,使得高层的模块不依赖低层的模块的实现细节的目的。有几个关键点如下:

    1. 高层的模块不应该依赖低层模块,两者都应该依赖抽象。
    2. 抽象不应该依赖细节。
    3. 细节应该依赖抽象。

5.接口隔离原则

  • 接口隔离原则是:客户端不应该依赖它不需要的接口。也是说,类间的依赖关系应该建立在最小的接口上。

6.迪米特原则、

  • 迪米特原则是:一个类应该对自己需要耦合或调用的类知道得最少,类的内部如何实现应该与调用者或者依赖者没关系。

MongoDB指令操作

显示操作

  • 显示数据表
1
$ show dbs
  • 显示collection操作
1
$ show collections

命名规则

  • 集合使用名称进行标识。集合名可以是满足下列条件的任意UTF-8字符串
  1. 集合名不能是空字符串(“”)

  2. 集合名不能包含\0字符(空字符),这个字符表示集合名的结束

  3. 集合名不能以”system.”开头,这是为系统集合保留的前缀

  4. 用户创建的集合不能在集合名中包含保留字符’$’

  • 数据库通过名称来标识
  1. 不能是空字符串(“”)

  2. 不得含有 / , \ , . , “ , * , < , > , : , | , ? , $ (一个空格) , \0 (空字符)

  3. 数据库名区分大小写

  4. 数据库名最多为64字节

安卓动画点击呈现水波效果

安卓5.0以下的版本实现

recycler_view_background.xml

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" >
<shape android:shape="rectangle">
<solid android:color="#cfd8dc"></solid>
</shape>
</item>
</selector>

5.0以上的版本实现为(ripple是5.0以上的版本新出现的,对于该图片应该放置在drawable-v21的文件夹下)

recycler_view_background.xml

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#cfd8dc">
<item android:drawable="@drawable/recycler_view_rectangle"/>
</ripple>

recycler_view_rectangle.xml

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FFFFFF" />
</shape>