LoopBack

ES6的出品为JS成为企业级语言扫清障碍,与之配套的需要一个真正的企业级框架。Express是一个精巧的微内核不足以支撑起一个大项目。
LoopBack 是建立在 Express 基础上的企业级 Node.js 框架,这个框架有以下特点:

  • 编写少量代码就能创建动态端到端的 REST API
  • 支持主流的数据源,例如 Mongodb、MySQL、SOAP 等和 REST API 的数据。
  • 一致化的模型关系和对 API 访问的权限控制
  • 可使用内置的用于移动应用场景下的地理定位、文件服务以及消息推送
  • 提供 Android、iOS 和 JavaScript 的 客户端SDK

LoopBack 作为一个面向企业级的 Web 框架,提供了更丰富的功能,方便添加模型,权限控制,连接数据源等操作。例如可以通过修改配置增加模型,并指定模型的数据源。它默认提供了一些基础模型,例如 User 这个模型包含了注册登录等逻辑,可以非常方便的继承这些内建模型,实现个性化的定制。它还提供了 Hook 编程的机制,提供了可视化的调试页面,自动生成对应的前端 SDK 。这些功能在开发大型 Web 服务的时候,帮助我们更容易查看和管理项目。

StrongLoop 是 IBM 的一家子公司,StrongLoop 的 LoopBack 框架能够轻松地连接数据并将数据公开为 REST 服务。它能够在图形(或命令行)界面中以可视方式创建数据模型,可以使用这些模型自动生成 REST API – 从而为 REST 服务层生成 CRUD 操作,无需编写任何代码。这很重要?因为它使 API 开发变得更容易,并显著减少了从概念到实现所需的时间。

在 LoopBack 框架中,是通过所谓的“数据连接器”来获取和持久化数据。

Cloudant 是一个 DBaaS 服务,是一个衍生的 Apache CouchDB,所以将 LoopBack 连接器连接到 Cloudant 很简单,只需在应用程序中使用现有的社区驱动的 CouchDB LoopBack Connector。

StrongLoop 是生成 LoopBack 框架的工具程序,有的认为Strongloop 相当于Loopback + Angularjs。

相关文章

node 框架 LoopBack 教程
https://cnodejs.org/topic/57e5b2859c495dce044f397c
StrongLoop学习笔记(一)
https://blog.csdn.net/sanpo/article/details/45082089
使用Loopback3.0构建应用程序(一)
https://www.jianshu.com/p/763b1a847d2c
IBM developerWorks,可以其中搜索Strongloop
https://www.ibm.com/developerworks/cn/
Loopback 2.0
http://loopback.io/doc/en/lb2/Remote-methods.html#overview
Loopback 3.8.0
http://apidocs.strongloop.com/loopback/#model-remotemethod
http://apidocs.strongloop.com/loopback/#persistedmodel-create
https://loopback.io/doc/en/lb3/Remote-methods.html
find方法中Filter的where中的语法格式:
http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries

使用loopback做邮箱验证,LoopBack用户模型提供了注册新用户并确认其电子邮件地址的方法。您还可以使用loopback-component-passport模块将登录信息与Facebook,Google和其他第三方提供商集成。
https://www.jianshu.com/p/21b4d84fb7af

生成lb-services.js

Loopback生成lb-services.js,使用Loopback CLI工具的lb-ng命令

$ lb-ng server.js lbservices/lb-services.js

代码示例

//自定义方法
Unit.remoteMethod(
        'deleteRecursive',
        {
            description:'',
            accessType: 'WRITE',
            accepts: {arg: 'id', type: 'string',description: '机组 id', http: {source: 'query'}},
            returns: [{arg: 'status', type: 'string'}],
            http: [
                {verb: 'delete', path: '/deleteRecursive'}
            ]
        }
    );
#source的选择有 body,path,query
//实现包装find的功能
    Project.findProjects=function(filter,callback) {
        console.log("findProjects",filter);
        var json=JSON.parse(filter);
        Project.find(json,function (err,data) {
            callback(null,data);
        });
    }
    Project.remoteMethod('findProjects', {
            description:'查询工程列表',
            accessType: 'READ',
            accepts: {arg: 'filter', type: 'string',description: '查询条件', http: {source: 'query'}},
            returns: {type: 'array', root: true},
            http: [
                {verb: 'get', path: '/findProjects'}
            ]
        }
    );

ES6

ECMAScript 6.0(简称 ES6)是 JavaScript 语言的下一代标准,在 2015 年 6 月正式发布。它的目标是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ECMAScript 和 JavaScript 的关系,大体上前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 Jscript 和 ActionScript)。在日常场合这两个词是可以互换的。

从软件工程角度来看,ES6就相当于当年的Java5,是历史性的发展,从此可以用js做大型项目了。事实上各大主流浏览器现在已经支持大部分新特性了,后端的Node.js更是可以直接使用ES6的绝大多数语法。

Babel

Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在现有环境执行。这意味着可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。

// 转码前,箭头函数
input.map(item => item + 1);

// 转码后,普通函数
input.map(function (item) {
  return item + 1;
});

变量声明方式 let/const

ES6中全面使用let/const替换var。使用let声明一个值会被改变的变量,使用const声明一个值不会被改变的变量,即常量。

与var不同,新的变量声明方式带来了一些不一样的特性,其中最重要的两个特性就是提供了块级作用域与不再具备变量提升。let或const变量一定要在声明后面使用,否则报错is not defined。在实际使用中,尽量避免使用变量提升的特性带来的负面影响。只有在面试题中,才会对变量提升不停的滥用。
var声明的变量仍旧是函数级作用域。

var和let在for循环中的应用:
下面的代码如果使用var,最后输出的是10。

var a = [];
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 10
上面代码中最后一行输出的结果是10。变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。每一次循环,变量i的值都会发生改变,而循环内被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。

如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。
var a = [];
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i);
  };
}
a[6](); // 6

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。

既然使用let使的每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
结果:
// abc
// abc
// abc

箭头函数

当函数直接被return时,可以省略函数体的括号。

// es5
var fn = function(a, b) {
    return a + b;
}
// es6 6
const fn = (a, b) => a + b;

箭头函数可以替换函数表达式,但是不能替换函数声明。
箭头函数中没有this。在ES6中,默认采用严格模式,this不会自动指向window对象了,this就只能是undefined。

模板字符串

模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。在其中使用 ${} 来包裹一个变量或者一个表达式。

const a = 20;
const b = 30;
const str = `${a}+${b}=${a+b}`;

解构赋值

获取对象属性值都可以使用解析结构来减少代码量。本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。如果解构不成功,变量的值就等于undefined。

const props = {
    className: 'tiger-button',
    loading: false,
    clicked: true
}
//取对象中的值到变量
const { loading, clicked } = props;

//数组以序列号一一对应,这是一个有序的对应关系。
const arr = [1, 2, 3];
const [a, b, c] = arr;
let [a, b, c] = [1, 2, 3];

如果变量名与属性名不一致,必须使用完整写法。也就是说,对象的解构赋值的内部机制是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

let { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。

遍历 Map 结构:

任何部署了 Iterator 接口的对象,都可以用for…of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。

const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
  console.log(key + " is " + value);
}
// first is hello
// second is world

// 只获取键名
for (let [key] of map) {
  // ...
}
// 只获取键值
for (let [,value] of map) {
  // ...
}

默认值

变量默认值

const { loading = false, clicked } = props;

函数参数默认值
在实际开发中给参数添加适当的默认值,可以让我们对函数的参数类型有一个直观的认知。

function add(x = 20, y = 30) {
    return x + y;
}

ES6 内部使用严格相等运算符(===),判断只有当参数严格等于undefined,对会取值为默认值。

展开运算符

在ES6中用…来表示展开运算符。展开运算符可以将数组方法或者对象进行展开。

const arr1 = [1, 2, 3];
const arr2 = [...arr1, 10, 20, 30];//arr2 结果变成了[1, 2, 3, 10, 20, 30];
const obj1 = {
  a: 1,
  b: 2, 
  c: 3
}
const obj2 = {
  ...obj1,
  d: 4,
  e: 5,
  f: 6
}// 结果类似于 const obj2 = Object.assign({}, obj1, {d: 4})

展开运算符还可以声明为变量并接收数据

const props = {
  size: 1,
  src: 'xxxx',
  mode: 'si'
}
const { size, ...others } = props;

展开运算符还用在函数的参数中表示函数的不定参。只有放在最后才能作为函数的不定参,否则会报错。

const add = (a, b, ...more) => {
    return more.reduce((m, n) => m + n) + a + b
}
console.log(add(1, 23, 1, 2, 3, 4, 5)) // 39

对象字面量写法

ES6针对对象字面量做了许多简化语法的处理。
当属性名与值的变量名相同时,在对象结构中可以只写属性名。对象中的方法也可以使用字面量写法。

const name = 'Jane';
const age = 20
// es5
var person = {
  name: name,
  age: age,
  getName: function getName() {
    return this.name;
  }
};
// es6
const person = {
  name,
  age,
  getName() { // 只要不使用箭头函数,this就还是我们熟悉的this
    return this.name
  }
}

还可以使用变量作为对象的键。

const field = 'rock';
const heat = '50%';
const music = {
  [field]: heat
}
console.log(music); // Object {rock: "50%"}

class

ES6为创建对象提供了新的语法糖,就是Class语法。

// ES5
// 构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// 原型方法
Person.prototype.getName = function() {
  return this.name
}

// ES6
class Person {
  constructor(name, age) {  // 构造函数
    this.name = name;
    this.age = age;
  }
  getName() {  // 原型方法
    return this.name
  }
}

babel会将ES6的写法编译成利用Object.defineProperty实现的方式,这个方法的具体用处大家可以在《JavaScript高级编程3》中学习了解,包括get,set,等都有详细的说明

除此之外,我们还需要特别注意在实际使用中的几种写法方式的不同,在下面的例子注释中,我说明了他们分别对应的ES5中的含义。

class Person {
constructor(name, age) { // 构造函数
this.name = name;
this.age = age;
}

getName() { // 这种写法表示将方法添加到原型中
return this.name
}

static a = 20; // 等同于 Person.a = 20

c = 20; // 表示在构造函数中添加属性 在构造函数中等同于 this.c = 20

// 箭头函数的写法表示在构造函数中添加方法,在构造函数中等同于this.getAge = function() {}
getAge = () => this.age

}
箭头函数需要注意的仍然是this的指向问题,因为箭头函数this指向不能被改变的特性,因此在react组件中常常利用这个特性来在不同的组件进行传值会更加方便。

继承 extends

只需要一个extends关键字,可以实现继承了,不用像ES5那样去担心构造函数继承和原型继承。

//es6
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  getName() {
    return this.name
  }
}

// Student类继承Person类
class Student extends Person {
  constructor(name, age, gender, classes) {
    super(name, age);
    this.gender = gender;
    this.classes = classes;
  }
  getGender() {
    return this.gender;
  }
}

还需要关注super方法。在继承的构造函数中必须调用一次super方法,它表示构造函数的继承,与ES5中利用call/apply继承构造函数是一样的功能。

// 构造函数中
// es6 ,super还可以直接调用父级的原型方法,如super.getName。
super(name, age);

// es5
Person.call(this);

Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
http://www.jianshu.com/p/fe5f173276bd

模块 Modules

1.引入模块

// src/index.js
import test1 from './test'

上面示例中,import表示引入一个模块,test 暂时理解为引入模块的名字。
引入这个动作执行时,test.js中的代码也执行了。如果在test.js中没有对外暴露任何接口,那么test1为一个空对象。
2.对外提供接口
ES6 modules使用export关键字对外提供接口。
export default对包暴露了一个对象。import 后跟的对象就默认是export default暴露的对象。

export default {
    num,
    arr,
    obj,
    foo
}

还可以通过export暴露更多方法与属性。

export const bar = () => {}
export const zcar = 12345;

可以一次引入获得模块暴露的所有接口。

import * as test1 from './test';

上面示例中的 * 表示所有,这是比较常用的通配符,as表示别名,* as test1的意思是将test.js暴露的所有接口组成的对象,命名为test1。
在引入中使用解析结构,下面示例中test1仍然表示为export default暴露的对象,而 { bar, zcar }则表示从整个返回对象中去取得对应的接口。

import test1, { bar, zcar } from './test';

Symbol

ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

let s = Symbol();

typeof s
// "symbol"

上面代码中,变量s就是一个独一无二的值。typeof运算符的结果,表明变量s是 Symbol 数据类型,而不是字符串之类的其他类型。
注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。

Set和Map

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
  console.log(i);
}
// 2 3 5 4

WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。

首先,WeakSet 的成员只能是对象,而不能是其他类型的值。
其次,WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,是一种更完善的 Hash 结构实现。
Map 遍历有一个forEach方法,与数组的forEach方法类似。

map.forEach(function(value, key, map) {
  console.log("Key: %s, Value: %s", key, value);
});

WeakMap结构与Map结构类似,也是用于生成键值对的集合。

Generator 函数

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

前端基础进阶系列
https://www.jianshu.com/p/cd3fee40ef59
ECMAScript 6 入门
http://es6.ruanyifeng.com/

JS基础

字符串和整数混合运算

‘1’+1 结果 ’11’
‘1’-1 结果 0
1+1+’1′ 结果 ’21’
‘1’+1+1 结果 ‘111’

类型转换

1.转Number
数字:转换后还是数字
字符串:如果可以被解析为数值,则为相应的数值,如果不能,则是 NaN,如果是空字符串那就是0
布尔值:true为1,false为0
undefined:NaN
null:0
object:先执行valueOf,看是否能转换,如果不可以再执行toString,看是否可以转换,如果不可以报错
2.转String
数字:转换成对应的字符串
字符串:还是对应的字符串
布尔值:true为’true’,false为’false’
undefined:undefined
null:null
object:先执行toString,看是否能转换,如果不可以再执行valueOf,看是否可以转换,如果不可以报错
3.转Boolean
下面这几个是false,其他都是true
NaN
null
undefined
0
“”
false

4.隐式类型转换
四则运算、判断语句中在隐式类型转换。

逻辑判断

=== 会先判断类型是否相同,再判断内容是否相同
== 只判断内容是否相同
1==’1′ 结果true

逻辑判断

给字符串添加属性并赋值,再打印此属性,值为undefined。这说明并不能给字符串增加属性。

JS基础知识(覆盖JS基础面试题)
https://yq.aliyun.com/articles/608880

Node应用

蓝牙

有经典蓝牙(classic)和低功耗蓝牙(Bluetooth Low Energy BLE).

BLE关键术语:每个蓝牙4.0的设备都是通过服务和特征来展示自己的。
service : 一个设备必然包含一个服务以上(至少包含一个服务,有可能包含更多的服务)。
characteristic : 包含在服务下, 描述该服务下特定的功能的, 一般拥有多个characteristic, 而且characteristic有类型, 读(read), 写(write), 通知(notify)等。
UUID(Universally Unique Identifier) : 设备的每一个service有相应的UUID, 同时每一个characteristic也有相应的UUID。

  • Service:服务,服务是一个集合的特点。例如有一个名为“心率监视器”的服务,其中包括诸如“心率测量”的特征。可以在bluetooth.org上找到现有基于GATT的个人资料和服务的列表。
  • GATT:通用属性简档,用于通过BLE链路发送和接收称为“属性”的短数据块的一般规范。所有当前的低能量应用配置文件都基于GATT。
    蓝牙SIG为低能量设备定义了许多配置文件 。 配置文件是设备在特定应用程序中的工作方式的规范。 请注意,设备可以实现多个配置文件。 例如,设备可以包含心率监视器和电池水平检测器。
  • ATT:属性协议,GATT建立在ATT之上,也称为GATT/ATT。ATT经过优化,可在BLE设备上运行,为此它使用尽可能少的字节。每个属性由通用唯一标识符(UUID)唯一标识,UUID是用于唯一标识信息的字符串ID的标准化128位格式。由ATT传送的属性被格式化为特征和服务。
  • 特征:特征含描述特征值的单个值和0-n个描述符。即每个特征值包含一个value 和多个 descriptor,在某个特征值的value中包含数据信息。
  • Descriptor:描述符,描述符是描述特征值的定义属性。

每个service包含一个或多个characteristic(特征值)

蓝牙常用开发步骤
https://www.jianshu.com/p/2809ead73d2a

蓝牙协议4.0、4.1、4.2的比较

SIG在2010年发布了4.0的specification,2013年发布了4.1的specification,2014年又发布了4.2的specification。从4.0版本起,革命性的加入了BLE协议部分,同时将2.1+EDR和3.0+HS全都包含在内。4.1和4.2在4.0的基础上做了改进,主要包括连接速度,传输效率等等,可以看出是向着适用于物联网的方向做的改进。
首先,相比4.0,4.1和4.2最明显的特点是增加了一个volume 7:Wireless Coexistence volume。主要介绍手机的无线共存测试。移动通讯采用4G-LTE标准后会占用2.4GHz频段,4.1和4.2的specification对此做出了测试。
4.1和4.2在4.0的基础上添加了IPv6和6LowPAN,搭载蓝牙芯片的设备可以取得在互联网上的唯一标记,与其他的联网设备进行通讯。
4.1和4.2提高了4.0的传输速率,4.0的协议栈规定了每包承载有效数据不大于20字节,4.2把这个数值扩大了10倍,最终将BLE的传输速率提高了2.5倍,不过据说要等硬件升级才能感受到这一低功耗高速率的传输方式。
4.1和4.2实现了主从一体,比如智能手环作为主和防丢器连接的时候,智能手环同时也可以作为从和智能手机相连。
4.1和4.2再有一个很重要的方面是改进了蓝牙连接的安全性。
物联网构成无线连接一定是组网灵活、低功耗、带宽适用和安全的,蓝牙在向着这样的方向发展。与wifi和zigbee相比,蓝牙无疑是最有竞争力成为物联网协议的无线连接规范。

NODE REPL

REPL(Read Eval Print Loop:交互式解释器)

Node 自带了交互式解释器,可以很好的调试 Javascript 代码,可以执行以下任务:

读取 – 读取用户输入,解析输入了Javascript 数据结构并存储在内存中。
执行 – 执行输入的数据结构
打印 – 输出结果
循环 – 循环操作以上步骤直到用户两次按下 ctrl-c 按钮退出。

http://www.runoob.com/nodejs/nodejs-repl.html

安卓手机向Mac通过蓝牙发送文件

mac系统偏好设置>共享 勾蓝牙共享,选接受并存储。

小米手环
http://www.cnblogs.com/wobeinianqing/p/5883135.html
http://www.jianshu.com/p/1dfba61e2a9d

其它
https://developer.android.com/guide/topics/connectivity/bluetooth-le.html
https://developer.android.com/guide/topics/connectivity/bluetooth.html?hl=zh-cn#TheBasics
https://developer.android.com/guide/topics/connectivity/bluetooth-le.html?hl=zh-cn
https://github.com/akexorcist/Android-BluetoothSPPLibrary
https://github.com/Jasonchenlijian/FastBle/issues
http://blog.csdn.net/dingjikerbo/article/details/49254573
https://github.com/dingjikerbo/BluetoothKit
http://blog.csdn.net/woshasanguo/article/details/41082395
https://my.oschina.net/tingzi/blog/215008
http://www.jianshu.com/p/2268cfedc051
http://www.cnblogs.com/cxk1995/p/5693979.html
http://blog.csdn.net/fangqiangqi/article/details/51510115
https://books.google.ca/books?id=vw9JDwAAQBAJ&pg=PT164&lpg=PT164&dq=%E8%93%9D%E7%89%99%E4%B8%BB%E4%BB%8E%E6%A8%A1%E5%BC%8F+%E4%B8%80%E5%AF%B9%E5%A4%9A&source=bl&ots=qIFm7_6O47&sig=gd0F710X2JwmVK5905XUh9xI0mk&hl=zh-CN&sa=X&redir_esc=y#v=onepage&q=%E8%93%9D%E7%89%99%E4%B8%BB%E4%BB%8E%E6%A8%A1%E5%BC%8F%20%E4%B8%80%E5%AF%B9%E5%A4%9A&f=false

蓝牙通讯过程

经典蓝牙和BLE的流程是不同的。
经典模式: 1.扫描设备. 2.建立设备连接. 3.建立socket连接. 4.发送和接收数据. 5.通讯完毕关闭连接清理缓存.
BLE 比经典模式复杂很多。
作为一个中心角色要实现完整的通讯,一般要经过这样几个步骤:
建立中心角色 -> 扫描外设(discover)—> 连接外设(connect) —> 扫描外设中的服务和特征(discover) —> 与外设做数据交互(explore and interact) —> 断开连接(disconnect)。

一个设备里的服务和特征往往比较多,大部分情况下我们只会在发现服务和特征的回调里去匹配关心其中几个。
数据的读分为两种,一种是直接读(reading directly),另外一种是订阅(subscribe)。具体用哪一种要看具体的应用场景以及特征本身的属性。特征有个properties字段(characteristic.properties),它是一个整型值,可能以下一些定义:

enum {  
     CBCharacteristicPropertyBroadcast = 0x01,  
     CBCharacteristicPropertyRead = 0x02,  
     CBCharacteristicPropertyWriteWithoutResponse = 0x04,  
     CBCharacteristicPropertyWrite = 0x08,  
     CBCharacteristicPropertyNotify = 0x10,  
     CBCharacteristicPropertyIndicate = 0x20,  
     CBCharacteristicPropertyAuthenticatedSignedWrites = 0x40,  
     CBCharacteristicPropertyExtendedProperties = 0x80,  
     };

如果要交互的特征的properties的值是0x10,表示只能用订阅的方式来接收数据。

RSSI

为什么无线信号(RSSI)是负值
答:其实归根到底为什么接收的无线信号是负值,这样子是不是容易理解多了。因为无线信号多为mW级别,所以对它进行了极化,转化为dBm而已,不表示信号是负的。1mW就是0dBm,小于1mW就是负数的dBm数。

弄清信号强度的定义就行了:
RSSI:接收信号强度 Received Signal Strength Indicator
Rss=10logP, 只需将接受到的信号功率P代入就是接收信号强度(灵敏度)。
RSSI的单位是dbm,假设发射功率取最大值为1mw,那么RSSI的值为0,也就是说距离蓝牙最近时在理想状态下所获取的RSSI的值为0,但在实际中基本不会存在这个理想状态,因此RSSI的值基本都为负数。而在蓝牙中,当距离很近时,所收到的RSSI的信号值大约在-50dbm。

UUID

每个蓝牙4.0的设备都是通过服务和特征来展示自己。一个设备必然包含一个或多个服务,每个服务下面又包含若干个特征。特征是与外界交互的最小单位。例如一台蓝牙4.0设备,用特征A来描述自己的出厂信息,用特征B来与收发数据。
服务和特征都是用UUID来唯一标识的,国际蓝牙组织为一些很典型的设备(比如测量心跳和血压的设备)规定了标准的service UUID(特征的UUID比较多,这里就不列举了),例如:

#define     BLE_UUID_ALERT_NOTIFICATION_SERVICE   0x1811  
 #define     BLE_UUID_BATTERY_SERVICE   0x180F  
 #define     BLE_UUID_BLOOD_PRESSURE_SERVICE   0x1810  
 #define     BLE_UUID_CURRENT_TIME_SERVICE   0x1805  
 #define     BLE_UUID_CYCLING_SPEED_AND_CADENCE   0x1816  
 #define     BLE_UUID_DEVICE_INFORMATION_SERVICE   0x180A  
 #define     BLE_UUID_GLUCOSE_SERVICE   0x1808  
 #define     BLE_UUID_HEALTH_THERMOMETER_SERVICE   0x1809  
 #define     BLE_UUID_HEART_RATE_SERVICE   0x180D  
 #define     BLE_UUID_HUMAN_INTERFACE_DEVICE_SERVICE   0x1812  
 #define     BLE_UUID_IMMEDIATE_ALERT_SERVICE   0x1802  
 #define     BLE_UUID_LINK_LOSS_SERVICE   0x1803  
 #define     BLE_UUID_NEXT_DST_CHANGE_SERVICE   0x1807  
 #define     BLE_UUID_PHONE_ALERT_STATUS_SERVICE   0x180E  
 #define     BLE_UUID_REFERENCE_TIME_UPDATE_SERVICE   0x1806  
 #define     BLE_UUID_RUNNING_SPEED_AND_CADENCE   0x1814  
 #define     BLE_UUID_SCAN_PARAMETERS_SERVICE   0x1813  
 #define     BLE_UUID_TX_POWER_SERVICE   0x1804  
 #define     BLE_UUID_CGM_SERVICE   0x181A

但还有很多设备并不在这个标准列表里,例如体脂秤血压计。蓝牙设备硬件厂商通常都会提供他们的设备里面各个服务(service)和特征(characteristics)的功能。比如哪些是用来交互(读写),哪些可获取模块信息(只读)等。

集成测试

持续集成

持续集成是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

Jenkins是基于Java开发的一种持续集成工具,用于监控持续重复的工作,功能包括:
1、持续的软件版本发布/测试项目。
2、监控外部调用执行的工作。

Jenkins使用极低的代价能部署一个软件,自动化进行项目构建,项目测试,静态扫描,上传构建,远程备份,发布。很多时候只需要点击立即构建,就进行了项目的部署操作。
它还提供权限管理,可以让不同的人员负责各自的项目建构。多项目构建,可以一次性完成多个项目的构建。

官方地址:https://jenkins.io/
docker:https://hub.docker.com/r/jenkins/jenkins/

Jenkins的原理,如果在新建的时候指定了Maven项目和代码的Git地址,Jenkins首先会通过Git将代码clone到本地,然后执行在Build中指定的pom.xml文件和指定的命令。

https://blog.csdn.net/bntX2jSQfEHy7/article/details/78743664

Jenkins 启动一直显示 Jenkins正在启动,请稍后…,深度修改配置中的访问的网站地址。
https://blog.csdn.net/heatdeath/article/details/79733611

对于自动安装失败的插件,可以在错误中找到hpi文件,通过手动下载然后在高级页面中上传插件的方式安装。

配置Publish Over SSH

在“系统设置”中配置“Publish Over SSH”。ssh的配置可使用密码,此时Passphrase中要填写应用服务器root等账号的密码。ssh的配置也可以使用密钥,使jenkins服务器可免密码直接登陆应用服务器。在配置密钥之前要先配置好jenkins服务器和应用服务器的密钥认证。在jenkins服务器上使用ssh-keygen -t rsa命令生成密钥对。在docker部署的jenkins/jenkins:lts中成功生成信息如下:

Your identification has been saved in /var/jenkins_home/.ssh/id_rsa.
Your public key has been saved in /var/jenkins_home/.ssh/id_rsa.pub.

将jenkins服务器的公钥id_rsa.pub中的内容复制到应用服务器/root/.ssh/authorized_keys文件中,在应用服务器上重启ssh服务service sshd restart。
解释两个不大明确的配置项:
Passphrase:生成rsa密钥时的密码。
Path to key:jenkins服务器上私钥文件的路径。
配置完成后可点击“Test Configuration”测试到目标主机的连接,出现”success“则成功连接。
https://www.cnblogs.com/hanxiaohui/p/8796025.html
https://www.jianshu.com/p/0d805ed204e6

压力测试

JMeter在Mac下的安装
https://www.jianshu.com/p/bce9077d883c

全文检索

全文检索是将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。
这部分从非结构化数据中提取出然后重新组织的信息,我们称之索引。这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。

Lucene是apache下的一个开源的全文检索引擎工具包(类库)。它的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能。Lucene提供了完整的查询引擎和索引引擎,部分文本分析引擎。

更新依据的字段只能是字符串类型,StringField。

Document doc=new Document();
doc.add(new StringField("id","abcd",Field.Store.YES));
indexWriter.updateDocument(new Term("id","abcd"),doc);

Field.Store.YES或者NO是用来决定分词前的原内容是否存储。

lucene一个field多个条件查询
https://blog.csdn.net/guiyecheng/article/details/56484049

用lucene实现在一个(或者多个)字段中查找多个关键字
https://www.cnblogs.com/xudong-bupt/archive/2013/05/08/3065297.html

Elastic Search 概述(一)
https://blog.csdn.net/yezonggang/article/details/80064394

Elasticsearch的不足
http://dockone.io/article/3655

Docker Compose

docker-compose

docker-compose是Docker官方编排工具,用来管理和运行Docker容器,负责实现对Docker容器集群的快速编排。使用一个 Dockerfile 模板文件,可以很方便的定义一个单独的应用容器,而Compose则可以方便的实现需要多个容器相互配合来完成某项任务的情况。
Docker Compose将所管理的容器分为三层,工程(project),服务(service)以及容器(contaienr)。Docker Compose运行的目录下的所有文件(docker-compose.yml、extends文件或环境变量文件等)组成一个工程,默认名称为当前目录名。一个工程当中可包含多个服务,一个服务当中可包括多个容器实例。Docker Compose并没有解决负载均衡的问题,需要借助其他工具实现服务发现及负载均衡。
Compose 通过一个单独的 docker-compose.yml 模板文件(YAML格式)来定义一组相关联的应用容器为一个项目 (project)。
Compose 中有两个重要的概念:
服务(service):一个应用的容器,实际上可以包括若干运行相同镜像的容器 实例。
项目(project):由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义。
Compose 的默认管理对象是项目,通过子命令对项目中的一组容器进行便捷地生命周期管理。

Docker Compose是一个部署多个容器的简单必要的工具,使用一条简单的命令部署多个容器,简化了多容器的部署过程,使多容器移植变得简单可控。从其Roadmap可以看出,Docker Compose的目标是做一个生产环境可用的工具,包括服务回滚,多环境支持(dev/test/staging/prod),支持在线服务部署升级,防止服务中断并且监控服务使其始终运行在正确的状态。Roadmap中的另一个目标是更好的与Docker Swarm集成,目前版本存在的主要问题是无法保证处于多个主机的容器间正常通信因为目前不支持跨主机间容器通信。另一个问题是在Docker Compose中定义构建的镜像只存在在一台Docker Swarm主机上,无法做到多主机共享,因此目前需要手动构建镜像并上传到一个镜像仓库使多个Docker Swarm主机可以访问并下载镜像。相信随着Docker Compose的完善,其必将取代docker run成为开发者启动docker容器的首选。

安装 Docker Compose
https://docs.docker.com/compose/install/

https://blog.csdn.net/chengqiuming/article/details/80957521

Docker服务器端用Nginx作为反向代理并添加SSL证书(阿里云免费DV证书)
https://www.cnblogs.com/wushangjue/p/7810339.html
http://www.mamicode.com/info-detail-1703824.html
http://blog.51cto.com/11962757/2086760
http://www.lixiangfeng.com/blog/article/content/8595503
https://www.jianshu.com/p/2217cfed29d7
可能冒号后需要加个空格,不然在执行 docker-compose up -d后报错ERROR: yaml.scanner.ScannerError: mapping values are not allowed here。
创建 docker-compose.yml 文件,然后在当前目录下执行命令 docker-compose up -d 来启动各个服务。

version: "2"
services:
  mysql1:
    image: mysql:5.7.9
    container_name: mysql1
    restart: always
    ports:
      - 3306:3306
    volumes:
      #- /alidata/docker_work/mysql-cogrowth/conf.d:/etc/mysql/conf.d
      - /data/docker_work/cogrowth/mysql/data:/var/lib/mysql
    environment:
      #- TZ=Asia/Shanghai
      - MYSQL_ROOT_PASSWORD=123456
  php-fpm1:
    image: bitnami/php-fpm
    container_name: php-fpm1
    restart: always
    links:
      - mysql1:mysql-alias
    volumes:
      - /alidata/docker_work/www:/host/www
  nginx2:
    image: nginx:1.15.2
    container_name: nginx2
    restart: always
    ports:
      - 82:80
    volumes:
      - /alidata/docker_work/www/:/usr/share/nginx/html
  nginx1:
    image: nginx
    container_name: nginx1
    restart: always
    ports:
      - 81:80
      - 444:443
    volumes:
      - /alidata/docker_work/nginx/conf:/etc/nginx/conf.d
      - /alidata/docker_work:/host
    links:
      - php-fpm1:php-fpm-alias
  phpMyAdmin1:
    image: phpmyadmin/phpmyadmin:4.8
    container_name: phpMyAdmin1
    restart: always
    ports:
      - 8085:80
    environment:
      - PMA_HOSTS=172.19.0.10,172.19.0.9
      - PMA_PORTS=3306,3306
      - PMA_VERBOSES=master,slave
  hksmart-inout:
    image: node:6.15
    container_name: hksmart-inout
    restart: always
    command:
      - /bin/sh
      - -c
      - |
        cd /hksmart/hksmart-inout
        npm install
        node inout_server.js
    volumes:
      - /alidata/docker_work/node/hksmart/hksmart_inout:/hksmart/hksmart-inout

注意在此环境下的Php连接mysql,需要使用mysqli_connect。
phpmyadmin的PMA_PORTS中使用的是docker容器内部的端口。

docker-machine

VirtualBox和Docker的区别

VirtualBox和Docker的区别,一句话总结就是VirtualBox虚拟化硬件,Docker虚拟化操作系统。
VirtualBox,是创建硬件虚拟化的软件。通常情况下,一个操作系统运行在硬件上,其中硬件和操作系统之间的通信是通过移动数据到内存地址,然后发出指令来通知可使用该数据的硬件(或者是数据在被读取时)。 在VirtualBox(或其它虚拟机)设置的环境中,那些内存地址实际上是虚拟机软件自身的内存区域,并且那些指令是由虚拟机而不是直接由底层的CPU解释的。实际结果是,你在VirtualBox中运行一个操作系统,对于这个操作系统来说,VirtualBox程序看起来像一台完整计算机,硬件以及所有配件都有。实际上它不知道自己是在另一个程序中运行的。

Docker,则是不进行硬件的虚拟化。相反,它的作用是创建一个文件系统,使其看起来像一个普通的Linux文件系统,并且运行应用程序在一个所有文件和资源都在文件系统内的锁定环境中。事实上,该应用程序的容器并不模仿任何硬件,应用程序仍然在硬件上运行,它只是隔离了应用程序并允许您可以运行该应用程序跟特定的并且完全不是主机操作系统的软件和第三方库合作。这意味着,在启动或停止Docker应用程序时几乎没有开销,因为它们不需要预先分配的内存和磁盘空间等等。因此Docker容器很容易设置或者拆除。此外,容器在假装需要系统中各种硬件组件上运行软件的时候并不浪费任何开销 – 它是直接使用硬件的。

yum安装VirtualBox
配置yum源:
vim /etc/yum.repos.d/virtualbox.repo
输入

[virtualbox]
name=Oracle Linux / RHEL / CentOS-$releasever / $basearch - VirtualBox
baseurl=http://download.virtualbox.org/virtualbox/rpm/el/$releasever/$basearch
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://www.virtualbox.org/download/oracle_vbox.asc

然后用yum直接安装:
yum install VirtualBox-5.2

CentOS7下minikube start后VirtualBox执行报错
https://blog.csdn.net/jiekou0376/article/details/80454808

[root@openshift ~]# docker-machine create –driver virtualbox virtualbox1
Running pre-create checks…
Error with pre-create check: “This computer doesn’t have VT-X/AMD-v enabled. Enabling it in the BIOS is mandatory”

Docker Swarm

Docker Swarm 和 Docker Compose 都是 Docker 官方容器编排项目,不同的是,Docker Compose 是一个在单个服务器或主机上创建多个容器的工具,而 Docker Swarm 则可以在多个服务器或主机上创建容器集群服务,对于微服务的部署 Docker Swarm 更加适合。

【Docker】 Swarm简单介绍
https://www.cnblogs.com/franknihao/p/8490416.html

Swarm就是一组运行docker的机器,并联合成为一个集群。当启动Swarm集群之后,docker命令会通过Swarm manager执行在整个集群之上。Swarm集群的机器可以是物理机,也可以是虚拟机,当加入Swarm集群之后,被称为nodes。
Swarm managers有两种不同的方式运行container:第一种emptiest node,尽量使用少的机器部署容器;第二种global,确保每一台机器上都会运行一个容器的实例。可以在docker-compose.yml中指定运行的模式。
Swarm managers是集群的核心控制节点,它负责执行命令,授权新机器加入集群。Worker节点只负责提供资源。
docker可以很方便的切换为 swarm mode,切换的命令是docker swarm init,一旦切换为 swarm mode后,当前的机器角色就变为Swarm managers。

docker-compose 命令

#一次重启多个服务
docker-compose restart service1 service2

NTP

SNTP是简单网络时间协议(Simple Network Time protocol)的简称,它是目前Internet网上实现时间同步的一种重要工程化方法。
SNTP协议采用客户/服务器工作方式,服务器通过接收GPS信号或自带的原子钟作为系统的时间基准,客户机通过定期访问服务器提供的时间服务获得准确的时间信息,并调整自己的系统时钟,达到网络时间同步的目的。客户和服务器通讯采用UDP协议,端口为123。

验证ntp服务:ntpq -p
查看当前时间:date -R

查找当前地区最适合的时间服务器:打开网站 http://www.pool.ntp.org/zone/asia,会在页面上部推荐一组最合适的同步服务器。在配置ntp服务时可以把它们复制粘贴到etc/ntp.conf中。

对时请求时设置超时时间在1秒内,超时即对时不成功,保证时间精度。
https://blog.csdn.net/hexiangqwqwqw/article/details/49801573
CentOS下NTP时间服务器搭建
http://blog.51cto.com/superpcm/2089888
Centos7配置ntp时间服务器
https://blog.csdn.net/zzy5066/article/details/79036674
docker 搭建ntp服务器
https://www.cnblogs.com/liubin0509/p/6282858.html

java实现NTP客户端

添加依赖commons-net

<!-- https://mvnrepository.com/artifact/commons-net/commons-net -->
<dependency>
    <groupId>commons-net</groupId>
    <artifactId>commons-net</artifactId>
    <version>3.6</version>
</dependency>

获取时间

String getDateTime(){
        try {
            NTPUDPClient timeClient = new NTPUDPClient();
            InetAddress timeServerAddress = InetAddress.getByName("cn.pool.ntp.org");
            TimeInfo timeInfo = timeClient.getTime(timeServerAddress);
            TimeStamp timeStamp = timeInfo.getMessage().getTransmitTimeStamp();
            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return dateFormat.format(timeStamp.getDate());
        } catch (UnknownHostException e) {
            e.printStackTrace();
            System.out.println("与ntp服务器同步时间错误!");
            return dateFormat.format(new Date());
        } catch (IOException e) {
            System.out.println("与ntp服务器同步时间错误!");
            return dateFormat.format(new Date());
        }
    }

Tomcat

部署tomcat

到http://tomcat.apache.org/下载tomcat,下载后解压到数据盘,执行bin/startup.sh来启动tomcat。注意设置startup.sh和catalina.sh的可执行权限。

./startup.sh
./shutdown.sh

shutdown后要等一会完全关闭后再重新启动。

遇到了tomcat异常挂掉的问题

有权限问题
java.util.logging.ErrorManager: 4
java.io.FileNotFoundException: /usr/local/tomcat/logs/catalina.2017-06-11.log (Permission denied)
有内存不够的问题
[root@VM_5_53_centos logs]# service tomcat restart
Stoping Tomcat
Using CATALINA_BASE: /usr/local/tomcat
Using CATALINA_HOME: /usr/local/tomcat
Using CATALINA_TMPDIR: /usr/local/tomcat/temp
Using JRE_HOME: /usr/java/jdk1.8.0_92
Using CLASSPATH: /usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/tomcat-juli.jar
Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000eb550000, 178978816, 0) failed; error=’Cannot allocate memory’ (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 178978816 bytes for committing reserved memory.
# An error report file with more information is saved as:
# //hs_err_pid14383.log
waiting for processes to exit

设置并发连接数

server.xml中在配置中,和连接数相关的参数有:
minProcessors:最小空闲连接线程数,用于提高系统处理性能,默认值为10
maxProcessors:最大连接线程数,即:并发处理的最大请求数,默认值为75
acceptCount:允许的最大连接数,应大于等于maxProcessors,默认值为100
enableLookups:是否反查域名,取值为:true或false。为了提高处理能力,应设置为false
connectionTimeout:网络连接超时,单位:毫秒。设置为0表示永不超时,但这样设置有隐患的。通常可设置为30000毫秒。
其中和最大连接数相关的参数为maxProcessors和acceptCount。如果要加大并发连接数,应同时加大这两个参数。
web server允许的最大连接数还受制于操作系统的内核参数设置,通常Windows是2000个左右,Linux是1000个左右。

设置内存占用

tomcat默认可以使用的内存为128MB,在较大型的应用项目中是不够的。
Unix下,在文件{tomcat_home}/bin/catalina.sh的前面,例如增加如下设置:JAVA_OPTS=’-Xms256m -Xmx512m’,表示初始化内存为256MB,可以使用的最大内存为512MB。

对虚拟路径配置二级域名

在tomcat/conf/server.xml中增加一个Host节点,把name=”localhost”修改为域名,如下:

<Host name="domain2.brogrammer.cn" appBase="webapps" unpackWARs="true" autoDeploy="true">
<Context path="" docBase="ReadAdmin" reloadable="false"/>
</Host>

Eclipse中配置J2EE Web module的服务容器

Eclipse是在 “服务偏好/Server/Runtime Environments” 中添加。
注意开发中和生产中要使用同一个Tomcat版本,因为编译可能要使用servlet-api.jar,要保证编译和运行版本一致,它存在于tomcat/lib中。

Tomcat配置https

1.将证书.jks文件放到tomcat/conf目录下
2.编辑conf/server.xml文件,在http和ajp节点中增加redirectPort
<Connector port=”80″ protocol=”HTTP/1.1″ connectionTimeout=”20000″ redirectPort=”443″ />
<Connector port=”8009″ protocol=”AJP/1.3″ redirectPort=”443″ />
3.编辑conf/server.xml文件,增加https节点
<Connector port=”443″ protocol=”org.apache.coyote.http11.Http11NioProtocol” maxThreads=”150″ SSLEnabled=”true”>
<SSLHostConfig>
<Certificate certificateKeystoreFile=”conf/ xxx.jks” certificateKeyAlias=”www.abc.com” certificateKeystorePassword=”” type=”RSA” />
</SSLHostConfig>
</Connector>

临时目录问题

Caused by: java.io.IOException: The temporary upload location [/tmp/tomcat.**/work/Tomcat/localhost/ROOT] is not valid
原因分析:临时文件夹无效,系统可能清除了临时目录。

解决方案一:
重启服务,临时方案:会重新生成tomcat目录,但是生产环境不建议如此操作;

解决方案二:
1增加服务配置,自定义baseDir:
2启动时增加参数-Djava.io.tmpdir=自定义目录

解决方案三:
注入一个Bean,手动配置临时目录

/**
 * 文件上传临时路径
 */
 @Bean
 MultipartConfigElement multipartConfigElement() {
    MultipartConfigFactory factory = new MultipartConfigFactory();
    factory.setLocation("/app/pttms/tmp");
    return factory.createMultipartConfig();
}

拒绝连接

当代码中的本地方法出现错误时,例如 terminate called after throwing an instance of ‘std::out_of_range’,会导致tomcat服务重新启动。此时其它服务调用此服务时,可能会呈现出“拒绝连接”的错误。