tars-node:RPC框架分析(stream(部分),registry,utils,winston-tars,notify,logs,config,dyeing)

stream

TARS 框架的编解码工具

结构体的使用示例
我们演示结构体在三个典型场景的使用方法:
第一种场景:当结构体用作RPC函数的参数时。
由于rpc框架会自动对参数进行序列化,所以我们无需关心编解码,只需要按照普通的类一样,先new后赋值,然后传入参数直接调用RPC函数即可。
假如服务端有个RPC如下定义:

module TRom
{
  struct param {
    0 optional string sUserName;
    1 optional int iId;
  };
  interface process {
    int getUserLevel(Param userInfo, out int iLevel);
  };
};

安装上述方法生成tars编解码文件(生成文件名称为:Protocol.js)之后,按照如下方法调用对端服务。

var Tars = require("@tars/rpc").client;
var TRom = require("./Protocol.js").TRom;

var prx = Tars.stringToProxy(TRom.NodeJsCommProxy, "TRom.NodeJsTestServer.NodeJsCommObj@tcp -h 10.12.22.13 -p 8080 -t 60000");

var usr = new TRom.Param();
usr.sUserName = "KevinTian";
usr.iId = 10000;

prx.getUserLevel(usr).then(function (result) {
  console.log("success:", result);
}, function (result) {
  console.log("error:", result);
}).done();

第二种场景:对端非标准rpc框架,接受序列化的数据流作为参数。
在这种场景下需要我们队结构体进行序列化。还是以上面的tars文件作为例子,一般的方法如下:

//  客户端安装如下方法进行打包,然后将打包后的二进制数据发送到服务端
var Tars = require("@tars/stream");
var TRom = require("./Protocol.js").TRom;

var usr = new TRom.Param();
usr.sUserName = "KevinTian";
usr.iId = 10000;

var os = new Tars.OutputStream();
os.writeStruct(1, usr);

//  打包并得到发送的二进制数据流
var toSendBuffer = os.getBinBuffer().toNodeBuffer();

客户端将toSendBuffer发送给服务端,并且服务端接受完毕之后按如下方法进行解码:

var Tars = require("@tars/stream");
var TRom = require("./Protocol.js").TRom;

var is = new Tars.InputStream(new Tars.BinBuffer(toSendBuffer));
var usr = is.readStruct(1,true, TRom.Param);

console.log("TRom.Param.sUserName", usr.sUserName);
console.log("TRom.Param.iId", usr.iId);

第三种场景:对方服务要求数据流使用Tup协议,并且已经约定好了各个变量的名字。我们可以按如下的方法进行编码:

//  客户端根据约定的名字,将结构体放入Tup中
var Tars = require("@tars/stream");
var TRom = require("./Protocol.js").TRom;

var usr = new TRom.Param();
usr.sUserName = "KevinTian";
usr.iId = 10000;

var tup_encode = new Tars.Tup();
tup_encode.writeStruct("userInfo", usr);

//  打包并得到发送的二进制数据流
var toSendBuffer = tup_encode.encode(true).toNodeBuffer();

客户端将toSendBuffer发送给服务端,并且服务端接受完毕之后按如下方法进行解码:

var Tars = require("@tars/stream");
var TRom = require("./Protocol.js").TRom;

var tup_decode = new Tars.Tup();
tup_decode.decode(new Tars.BinBuffer(toSendBuffer));

var usr = tup_decode,readStruct("userInfo", TRom.Param);

console.log("TRom.Param.sUserName", usr.sUserName);
console.log("TRom.Param.iId", usr.iId);

06-复杂类型-vector(数组)的使用方法说明

由于JavaScript原生的Array不支持tars中的一些特殊化操作,所以我们对它进行了一次封装。开发者可以按下述的代码理解:

[stream].List = function(proto) 
{
  this.proto = proto;
  this.value = new Array();
  this.push = function(value) { this.value.push(value) };
}
属性 描述
value Js中的Array数据类型。Tars.List实际是基于该Array进行的上层封装
length 返回数组中元素的数目

[stream].List对象属性

属性 描述
value Js中的Array数据类型。Tars.List实际是基于该Array进行的上层封装
length 返回数组中元素的数目
方法 描述
at 返回数组中指定位置的元素。
push 向数组的末尾添加一个元素。
forEach 当前数组的遍历方法,具体使用方法请参考后面的示例。
toObject 将List实例转化成基本的数据对象,具体使用方法请参考后面的实例。
readFromObject 将传入的数组处理后push到List实例中,具体使用方法请参考后面的示例。

[stream].List对象方法

方法 描述
at 返回数组中指定位置的元素。
push 向数组的末尾添加一个元素。
forEach 当前数组的遍历方法,具体使用方法请参考后面的示例。
toObject 将List实例转化成基本的数据对象,具体使用方法请参考后面的实例。
readFromObject 将传入的数组处理后push到List实例中,具体使用方法请参考后面的示例。

proto是Vector的类型原型(类型原型决定了在对Vector编解码时采用的方法,所以声明Vector的时候必须传入正确的类型原型)。
[stream].List的声明示例

var Tars = require("@tars/stream");

//  例子1: 声明vector<int32>
var va = new Tars.List(Tars.Int32);

//  例子2: 声明vector<string>
var vb = new Tars.List(Tars.String);

//  例子2: 声明vector<map<uint32, string>>
var vc = new Tars.List(Tars.Map(Tars.UInt32, Tars.String));

//  例子1: 声明vector<struct>,假设结构体名称为TRom.Param
var vd = new Tars.Vector(TRom.Param);

[stream].List的操作示例

var Tars = require("@tars/stream");
var ve = new Tars.List(Tars.String);

//  向数组添加元素
ve.push("TENCENT-MIG");
ve.push("TENCENT-SNG");
ve.push("TENCENT-IEG");
ve.push("TENCENT-TEG");

//  获取数组的长度
console.log("Length:", ve.length);

//  获取指定位置的元素
console.log("Array[1]:", ve.at(1));

//  遍历方法1:
ve.forEach(function (value, index, oArray) {
  console.log("Array[" + index + "]:", value);
});

//  遍历方法2:
for (var index = 0, len = ve.length; index < len; index++) {
  console.log("Array[" + index + "]:", ve.at(index);
};

//  toObject方法和readFromObject方法的详细例子可以参照sample/list路径下的test-list-c3文件
var user1 = new TRom.User_t();
user1.id = 1;
user1.name = 'x1';
user1.score = 1;

var user2 = new TRom.User_t();
user2.id = 2;
user2.name = 'x2';
user2.score = 2;

var user3 = new TRom.User_t();
user3.id = 3;
user3.name = 'x3';
user3.score = 3;

var userList1 = new Tars.List(TRom.User_t);

console.log('user1: ', user1);
console.log('user2: ', user2);

userList1.push(user1);
userList1.push(user2);

//  toObject方法
console.log('userList1: ', userList1.toObject());

var userList2 = new Tars.List(TRom.User_t);
// readFromObject方法
userList2.readFromObject([user1, user2, user3]);
console.log('userList2: ', userList2.toObject());

07-复杂类型-map(字典)的使用方法说明

由于JavaScript原生的Object不支持tars中的一些特殊化操作,所以我们对它进行了一次封装。开发者可按下述的代码理解:

[stream].Map = function(kpoto, vpoto) {
  var Map = function() {
    this._kproto = kpoto;
    this._vproto = vpoto;
    this.value = new Object();
    this.put = function(key, value) { this.insert(key, value); }
    ......
  }
  return new Map();
}
属性 描述
value Js中的Object数据类型。[stream],Map实际是基于该Object进行的上层封装。

[stream].Map对象属性

属性 描述
value Js中的Object数据类型。[stream],Map实际是基于该Object进行的上层封装。
方法 描述
insert 向字典中添加一个元素
set 同insert
put 同insert
remove 根据指定的key,从字典中删除对应的数值。
clear 清空当前字典。
has 根据指定的key,判断字典中是否包含对应的数值
size 返回当前字典中元素的数目
forEach 当前数组的遍历方法,具体使用方法请参考后面的示例。
toObject 将Map实例转化成基本的数据对象,具体方法请参考后面的示例。
readFromObject 将传入的对象处理后insert到Map实例中,具体使用方法参考后面的示例。

[stream].Map方法属性

方法 描述
insert 向字典中添加一个元素
set 同insert
put 同insert
remove 根据指定的key,从字典中删除对应的数值。
clear 清空当前字典。
has 根据指定的key,判断字典中是否包含对应的数值
size 返回当前字典中元素的数目
forEach 当前数组的遍历方法,具体使用方法请参考后面的示例。
toObject 将Map实例转化成基本的数据对象,具体方法请参考后面的示例。
readFromObject 将传入的对象处理后insert到Map实例中,具体使用方法参考后面的示例。

[stream].Map的声明示例

var Tars = require("@tars/stream");

//  例子1: 声明map<int32, int32>
var ma = new Tars.Map(Tars.Int32, Tars.Int32);

//  例子2: 声明map<int32, string>
var mb = new Tars.Map(Tars.Int32, Tars.String);

//  例子3: 声明map<string, string>
var mc = new Tars.Map(Tars.String, Tars.String);

//  例子4: 声明map<string, vector<int32>>
var md = new Tars.Map(Tars.String, Tars.List(Tars.Int32));

//  例子5: 声明map<string, map<int32, vector<string>>>
var me = new Tars.Map(Tars.Int32, Tars.Map(Tars.Int32, Tars.List(Tars.String)));

//  例子6: 声明map<string, struct>的方法,假设结构体名称为TRom.Param
var mf = new Tars.Map(Tars.String, TRom.Param);

[stream].Map的操作示例

var Tars = require("@tars/stream");

var mc = new Tars.Map(Tars.String, Tars.String);

//  向字典中添加元素
mc.insert("KEY-00", "TENCENT-MIG");
mc.insert("KEY-01", "TENCENT-IEG");
mc.insert("KEY-02", "TENCENT-TEG");
mc.insert("KEY-03", "TENCENT-SNG");

//  获取字典元素大小
console.log("SIZE:", mc.size());

//  判断字典是否有指定的值
console.log("Has:", mc.has("KEY-04"));

//  字典遍历
mc.forEach(function(key, value) {
  console.log("KEY:", key);
  console.log("VALUE:", value);
});

//  toObject方法和readFromObject方法的详细例子可以参照sample/map路径下的test-map-c5.js文件
var user1 = new TRom.User_t();
user1.id = 1;
user1.name = 'x1';
user1.score = 1;

var user2 = new TRom.User_t();
user2.id = 2;
user2.name = 'x2';
user2.score = 2;

var user3 = new TRom.User_t();
user3.id = 3;
user3.name = 'x3';
user3.score = 3;

var userMap1 = new Tars.Map(Tars.String, TRom.User_t);

userMap1.insert('user1', user1);
userMap1.insert('user2', user2);

//  toObject方法
console.log('userMap1: ', userMap1.toObject());

var userMap2 = new Tars.Map(Tars.String, TRom.User_t);
//  readFromObject方法
userMap2.readFromObject({
  'user1' : user1,
  'user2' : user2,
  'user3' : user3
});
console.log('userMap2: ', userMap2.toObject());

支持MultiMap类型
支持MultiMap类型,此类型允许以一个结构体作为Map的key。javascript原生对象没有办法表示此数据类型,因此此类型没有实现普通Map支持的toObject和readFromObject方法。
其操作实例如下:

//  构造Map柯旭
var msg = new Tars.Map(Test.StatMicMsgHead, Test.StatMicMsgBody);
msg.put(StatMicMsgHead1, StatMicMsgBody1);
msg.put(StatMicMsgHead2, StatMicMsgBody2);

//  tars编码
var os = new Tars.OutputStream();
os.writeMap(1, msg);

//  tars解码
var data = os.getBinBuffer().toNodeBuffer();

var is = new Tars.InputStream(new Tars.BinBuffer(data));
var ta = is.readMap(1, true, Tars.Map(Test.StatMicMsgHead, Test.StatMicMsgBody));

//  遍历Map结果集
ta.forEach(function (key, value) {
  console.log("KEY:", key.masterName, "VALUE.totalRspTime", value.totalRspTime);
});

//  根据值去获取  
var tb = ta.get(StatMicMsgHead2);
if (tb == undefined) {
  console.log("not found by name: StatMicMsgHead2");
} else {
  console.log(tb.totalRspTime);
}

08-复杂类型-二进制Buffer的使用方法说明

在浏览器中我们可以使用DataView和ArrayBuffer来存储和操作二进制数据。NodeJS为了提升性能,自身提供了一个Buffer类。为了方便Tars编解码,我们队Buffer类进行了一层封装。开发者可以按下述的代码来
理解。

[stream].BinBuffer = function (buffer) {
  this._buffer = (buffer != undefined && buffer instanceof Buffer) ? buffer : null;
  this._length = (buffer != undefined && buffer instanceof Buffer) ? buffer,length : 0; 
  this._capacity = this._length;
  this._position = 0;
}
属性 描述
length 获取该二进制Buffer的数据长度
capacity 获取该二进制Buffer在不重复分配内存的情况下,可容纳数据的最大长度
position 获取或者设置当前二进制Buffer的访问指针

[stream].BinBuffer对象属性

属性 描述
length 获取该二进制Buffer的数据长度
capacity 获取该二进制Buffer在不重复分配内存的情况下,可容纳数据的最大长度
position 获取或者设置当前二进制Buffer的访问指针
参数 数据类型 描述
srcBuffer NodeJS.Buffer $1600
offset Unit32 表示拷贝srcBuffer的起始位置
byteLength Unit32 表示从offset开始,从srcBuffer中拷贝的数据量

length和capacity的区别:
加入我们向BinBuffer中写入一个int32类型的数据。写成功之后,length和capacity的区别:由于BinBuffer类在第一次分配时使用默认的512长度来申请内存,此时capacity的值为512。length表示当前Buffer中存在真实数据的大小,此时length的值为4
[stream].BinBuffer方法属性
toNodeBuffer
函数定义:[stream].BinBuffer.toNodeBuffer()
函数作用:返回当前二进制Buffer的数据,该值为深拷贝的类型NodeJS.Buffer的数据
输入参数:无
返回数据:NodeJS.Buffer类型
print
函数定义:[stream].BinBuffer.print()
函数作用:以每行16个字节,并16进制的方式打印当前的Buffer
writeNodeBuffer
函数定义:[stream],BinBuffer.writeNodeBuffer(srcBuffer, offset, byteLength)
函数作用:向二进制Buffer中写入NodeJS.Buffer类数据
输入参数:

参数 数据类型 描述
srcBuffer NodeJS.Buffer $1600
offset Unit32 表示拷贝srcBuffer的起始位置
byteLength Unit32 表示从offset开始,从srcBuffer中拷贝的数据量
参数 数据类型 描述
value [stream].BinBuffer 表示二进制Buffer

函数说明:
[1]当前BinBuffer的length = length(原Buffer数据长度) + byteLength
[2]当前BinBuffer的position = position(原Buffer的位置指针) + byteLength
writeBinBuffer
函数定义:[stream].BinBuffer.writeBinBuffer(value)
函数作用:向二进制Buffer中写入[stream].BinBuffer类数据
输入参数:

参数 数据类型 描述
value [stream].BinBuffer 表示二进制Buffer
参数 数据类型 描述
value Int8 8位的整型数据

函数说明:
[1]当前BinBuffer的length = length(原Buffer数据长度) + value.length
[2]当前BinBuffer的position = position(原Buffer的位置指针) + value.length
writeInt8
函数定义:[stream],BinBuffer.writeInt8(value)
函数作用:向二进制Buffer中写入Int8类数据
输入参数:

参数 数据类型 描述
value Int8 8位的整型数据
参数 数据类型 描述
value Int16 16位的整型数据

函数说明:
[1]当前BinBuffer的 length = length(原Buffer数据长度) + 1
[2]当前BinBuffer的 position = position(原Buffer的位置指针) + 1
writeInt16
函数定义:[stream],BinBuffer.writeInt16(value)
函数作用:向二进制Buffer中写入Int16类数据
输入参数:

参数 数据类型 描述
value Int16 16位的整型数据
参数 数据类型 描述
value Int32 32位的整型数据

函数说明:
[1]当前BinBuffer的 length = length(原Buffer数据长度) + 2
[2]当前BinBuffer的 position = position(原Buffer的位置指针) + 2
[3]数据存储采用网络字节序
writeInt32
函数定义:[stream],BinBuffer.writeInt32(value)
函数作用:向二进制Buffer中写入Int32类数据
输入参数:

参数 数据类型 描述
value Int32 32位的整型数据
参数 数据类型 描述
value Int64 64位的整型数据

函数说明:
[1]当前BinBuffer的 length = length(原Buffer数据长度) + 4
[2]当前BinBuffer的 position = position(原Buffer的位置指针) + 4
[3]数据存储采用网络字节序
writeInt64
函数定义:[stream],BinBuffer.writeInt64(value)
函数作用:向二进制Buffer中写入Int64类数据
输入参数:

参数 数据类型 描述
value Int64 64位的整型数据
参数 数据类型 描述
value UInt8 8位的整型数据

函数说明:
[1]当前BinBuffer的 length = length(原Buffer数据长度) + 8
[2]当前BinBuffer的 position = position(原Buffer的位置指针) + 8
[3]数据存储采用网络字节序
writeUInt8
函数定义:[stream].BinBuffer.writeUInt8(value)
函数作用:向二进制Buffer中写入UInt8类数据
输入参数:

参数 数据类型 描述
value UInt8 8位的整型数据
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value UInt16(Number) 表示该变量的值,取值范围[0, 65535]

函数说明:
[1]当前BinBuffer的 length = length(原Buffer数据长度) + 1
[2]当前BinBuffer的 position = position(原Buffer的位置指针) + 1
writeUInt16
函数定义:[stream].OutputStream.writeUInt16(tag, value)
函数作用:向数据流写入一个UInt16类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value UInt16(Number) 表示该变量的值,取值范围[0, 65535]
参数 数据类型 描述
value UInt32 32位的整型数据

返回数据:void
writeUInt32
函数定义:[stream].BinBuffer.writeUInt32(value)
函数作用:向二进制Buffer中写入一个UInt32类型的变量
输入参数:

参数 数据类型 描述
value UInt32 32位的整型数据
参数 数据类型 描述
value Float 32位的单精度浮点数

函数说明:
[1]当前BinBuffer的 length = length(原Buffer数据长度) + 4
[2]当前BinBuffer的 position = position(原Buffer的位置指针) + 4
[3]数据存储采用网络字节序
writeFloat
函数定义:[stream].BinBuffer.writeFloat(value)
函数作用:向二进制Buffer中写入Float(32位,单精度浮点数)类数据
输入参数:

参数 数据类型 描述
value Float 32位的单精度浮点数
参数 数据类型 描述
value Double 64位的双精度浮点数

函数说明:
[1]当前BinBuffer的 length = length(原Buffer数据长度) + 4
[2]当前BinBuffer的 position = position(原Buffer的位置指针) + 4
[3]数据存储采用网络字节序
writeDouble
函数定义:[stream].BinBuffer.writeDouble( value)
函数作用:向二进制Buffer中写入Double(64位,双精度浮点数)类数据
输入参数:

参数 数据类型 描述
value Double 64位的双精度浮点数
参数 数据类型 描述
value String UTF8编码的字符串

函数说明:
[1]当前BinBuffer的 length = length(原Buffer数据长度) + 8
[2]当前BinBuffer的 position = position(原Buffer的位置指针) + 8
[3]数据存储采用网络字节序
writeString
函数定义:[stream].BinBuffer.writeString(value)
函数作用:向二进制Buffer中写入String(UTF8编码)类数据
输入参数:

参数 数据类型 描述
value String UTF8编码的字符串
参数 数据类型 描述
byteLength UInt32 字符串的字节长度

函数说明:
[1]当前BinBuffer的 length = length(原Buffer数据长度) + 字符串的字节长度
[2]当前BinBuffer的 position = position(原Buffer的位置指针) + 字符串的字节长度
readInt8
函数定义:[stream].BinBuffer.readInt8()
函数作用:从二进制Buffer中,根据当前数据指针读取一个Int8类型的变量
输入参数:无
函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 1
readInt16
函数定义:[stream].BinBuffer.readInt16()
函数作用:从二进制Buffer中,根据当前数据指针读取一个Int16类型的变量
输入参数:无
函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 2
readInt32
函数定义:[stream].BinBuffer.readInt32()
函数作用:从二进制Buffer中,根据当前数据指针读取一个Int32类型的变量
输入参数:无
函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 4
readInt64
函数定义:[stream].BinBuffer.readInt64()
函数作用:从二进制Buffer中,根据当前数据指针读取一个Int64类型的变量
输入参数:无
函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 8
readUInt8
函数定义:[stream].BinBuffer.readUInt8()
函数作用:从二进制Buffer中,根据当前数据指针读取一个readUInt8类型的变量
输入参数:无
函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 1
readUInt16
函数定义:[stream].BinBuffer.readUInt16()
函数作用:从二进制Buffer中,根据当前数据指针读取一个readUInt16类型的变量
输入参数:无
函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 2
readUInt32
函数定义:[stream].BinBuffer.readUInt32()
函数作用:从二进制Buffer中,根据当前数据指针读取一个readUInt32类型的变量
输入参数:无
函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 4
readFloat
函数定义:[stream].BinBuffer.readFloat()
函数作用:从二进制Buffer中,根据当前数据指针读取一个Float(32位的单精度浮点数)类型的变量
输入参数:无
函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 4
readDouble
函数定义:[stream].BinBuffer.readDouble()
函数作用:从二进制Buffer中,根据当前数据指针读取一个Double(64位的双精度浮点数)类型的变量
输入参数:无
函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 8
readString
函数定义:[stream].BinBuffer.readString(byteLength)
函数作用:从二进制Buffer中,根据当前数据指针读取一个String(UTF8编码)类型的变量
输入参数:

参数 数据类型 描述
byteLength UInt32 字符串的字节长度
参数 数据类型 描述
byteLength UInt32 二进制Buffer的字节长度

函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 字符串的字节长度
[2]后台对字符串的编码需要使用UTF8字符集
readBinBuffer
函数定义:[stream].BinBuffer.readBinBuffer(byteLength)
函数作用:从二进制Buffer中,根据当前数据指针读取一个[stream].BinBuffer类型的变量
输入参数:

参数 数据类型 描述
byteLength UInt32 二进制Buffer的字节长度
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Boolean 表示该变量的值,取值范围[false, true]

函数说明:
[1]当前BinBuffer的 position = position(原Buffer的位置指针) + 二进制Buffer的字节长度
09 - 编码工具 - OutputStream的使用方法说明
构造函数
函数定义:[stream].OutputStream()
函数作用:声明一个输出流对象
输入参数:无
使用示例:var os = new [stream].OutputStream()
getBinBuffer
函数定义:var buffer = [stream].OutputStream.getBinBuffer()
函数作用:调用该函数获得打包后的二进制数据
输入参数:无
返回数据:返回打包后的二进制数据流,该返回值类型为[stream].BinBuffer
writeBoolean
函数定义:[stream].OutputStream.writeBoolean(tag, value)
函数作用:向数据流中写一个Boolean类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Boolean 表示该变量的值,取值范围[false, true]
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Int8(Number) 表示该变量的值,取值范围[-128, 127]

返回数据:void
writeInt8
函数定义:[stream].OutputStream.writeInt8(value)
函数作用:向数据流中写入Int8类型的数据
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Int8(Number) 表示该变量的值,取值范围[-128, 127]
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Int16(Number) 表示该变量的值,取值范围[-32768, 32767]

返回数据:void
writeInt16
函数定义:[stream].OutputStream.writeInt16(tag, value)
函数作用:向数据流中写入Int16类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Int16(Number) 表示该变量的值,取值范围[-32768, 32767]
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Int32(Number) 表示该变量的值,取值范围[-2147483648, 2147483647]

返回数据:void
writeInt32
函数定义:[stream].OutputStream.writeInt32(tag, value)
函数作用:向数据流中写一个Int32类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Int32(Number) 表示该变量的值,取值范围[-2147483648, 2147483647]
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value int64(Number) 表示该变量的值,取值范围[-9223372036854775808, 9223372036854775807]

writeInt64
函数定义:[stream].OutputStream.writeInt64(tag, value)
函数作用:向数据流中写一个Int64类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value int64(Number) 表示该变量的值,取值范围[-9223372036854775808, 9223372036854775807]
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value UInt8(Number) 表示该变量的值,取值范围[0, 255]

返回数据:void
writeUInt8
函数定义:[stream].OutputStream.writeUInt8(tag, value)
函数作用:向数据流写入一个UInt8类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value UInt8(Number) 表示该变量的值,取值范围[0, 255]
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value UInt16(Number) 表示该变量的值,取值范围[0, 65535]

返回数据:void
writeUInt16
函数定义:[stream].OutputStream.writeUInt16(tag, value)
函数作用:向数据流写入一个UInt16类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value UInt16(Number) 表示该变量的值,取值范围[0, 65535]
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value UInt32(Number) 表示该变量的值,取值范围[0, 4294967295]

返回数据:void
writeUInt32
函数定义:[stream].OutputStream.writeUInt32(tag, value)
函数作用:向数据流写入一个UInt32类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value UInt32(Number) 表示该变量的值,取值范围[0, 4294967295]
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Float(Number) 单精度浮点数,因为精度缺失问题,不推荐使用该类型

返回数据:void
writeFloat
函数定义:[stream].OutputStream.writeFloat(tag, value)
函数作用:向数据流中写入一个float(32位)类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Float(Number) 单精度浮点数,因为精度缺失问题,不推荐使用该类型
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Double(Number) 双精度浮点数,因为精度损失问题,不推荐使用该类型

返回数据:void
writeDouble
函数定义:[stream].OutputStream.writeDouble(tag, value)
函数作用:向数据流中写一个double(64位)类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value Double(Number) 双精度浮点数,因为精度损失问题,不推荐使用该类型
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value String 表示该变量的值,字符串编码字符集为UTF8

返回数据:void
writeString
函数定义:[stream].OutputStream.writeString(tag, value)
函数作用:向数据流中写入一个String类型的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value String 表示该变量的值,字符串编码字符集为UTF8
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value 自定义结构体 结构体必须是使用tars2node转换而成的,否则可能会因缺少辅助函数而导致编解码失败

返回数据:void
writeStruct
函数定义:writeStruct(tag, value)
函数作用:向数据流中写一个自定义结构体的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value 自定义结构体 结构体必须是使用tars2node转换而成的,否则可能会因缺少辅助函数而导致编解码失败
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value [stream]. BinBuffer BinBuffer是对NodeJs中的Buffer类的封装,同时集成了编码解码需要用到的辅助函数

返回数据:void
writeBytes
函数定义:[stream].OutputStream.writeBytes(tag, value)
函数作用:向数据流中写入一个char*或者vector<char>的变量
输入参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value [stream]. BinBuffer BinBuffer是对NodeJs中的Buffer类的封装,同时集成了编码解码需要用到的辅助函数
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value [stream].List(T) 该变量的类型原型

返回数据:void
writeList
函数定义:[stream].OutputStream.writeList(tag, value)
函数作用:向数据流中写一个类型为vector<T>(T不可为byte)的变量
函数参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value [stream].List(T) 该变量的类型原型
参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value [stream].Map(T, V) 该变量的类型原型

返回数据:void
writeMap
函数定义:[stream].OutputStream.writeMap(tag, value)
函数作用:向数据流中写一个类型为map<T, V>类型的字段。
函数参数:

参数 数据类型 描述
tag UInt8 表示该变量的数字标识,取值范围[0, 255]
value [stream].Map(T, V) 该变量的类型原型

返回数据:void

10-解码工具-InputStream的使用方法说明

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Boolean 表示读取变量不成功时的返回值,取值范围{false, true}

构造函数
函数定义:[stream].InputStream(binBuffer)
函数作用:声明一个输入流对象
输入参数:binBuffer欲解码的二进制数据流,该值类型必须为[stream].BinBuffer,而不能是NodeJs中实现的Buffer类。
使用示例: var is = new [stream].InputStream(new [stream].BinBuffer(Node.Buffer))
readBoolean
函数定义:var value = [stream].InputStream.readBoolean(tag, require, default)
函数作用:从数据流读取一个Boolean类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Boolean 表示读取变量不成功时的返回值,取值范围{false, true}
参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Int8 表示读取变量不成功时的返回值,取值范围[-128, 127]

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:Boolean,取值范围{false, true}
readInt8
函数定义:[stream].InputStream.readInt8(tag, require, default)
函数作用:从数据流读取一个Int8类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Int8 表示读取变量不成功时的返回值,取值范围[-128, 127]
参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Int16 表示读取变量不成功时的返回值,取值范围[-32768, 32767]

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:Int8,取值范围[-128, 127]
readInt16
函数定义:[stream].InputStream.readInt16(tag, require, default)
函数作用:从数据流读取一个Int16类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Int16 表示读取变量不成功时的返回值,取值范围[-32768, 32767]
参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Int32 表示读取变量不成功时的返回值,取值范围[-2147483648, 2147483647]

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:Int16,取值范围[-32768, 32767]
readInt32
函数定义:[stream].InputStream.readInt32(tag, require, default)
函数作用:从数据流读取一个Int32类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Int32 表示读取变量不成功时的返回值,取值范围[-2147483648, 2147483647]
参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Int64 表示读取变量不成功时的返回值,取值范围[-9223372036854775808, 9223372036854775807]

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:Int32,取值范围[-2147483648, 2147483647]
readInt64
函数定义:[stream].InputStream.readInt64(tag, require, default)
函数作用:从数据流读取一个Int64类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Int64 表示读取变量不成功时的返回值,取值范围[-9223372036854775808, 9223372036854775807]
参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default UInt8 表示读取变量不成功时的返回值,取值范围[0, 255]

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:Int64(Number),取值范围[-9223372036854775808, 9223372036854775807]
readUInt8
函数定义:[stream].InputStream.readUInt8(tag, require, default)
函数作用:从数据流读取一个UInt8类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default UInt8 表示读取变量不成功时的返回值,取值范围[0, 255]
参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default UInt16 表示读取变量不成功时的返回值,取值范围[0, 65535]

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:UInt8(Number),取值范围[0, 255]
readUInt16
函数定义:[stream].InputStream.readUInt16(tag, require, default)
函数作用:从数据流读取一个UInt16类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default UInt16 表示读取变量不成功时的返回值,取值范围[0, 65535]
参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default UInt16 表示读取变量不成功时的返回值,取值范围[0, 4294967295]

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:UInt16(Number),取值范围[0, 65535]
readUInt32
函数定义:[stream].InputStream.readUInt32(tag, require, default)
函数作用:从数据流读取一个UInt32类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default UInt16 表示读取变量不成功时的返回值,取值范围[0, 4294967295]
参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Float 表示读取变量不成功时的返回值

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:UInt32(Number),取值范围[0, 4294967295]
readFloat
函数定义:[stream].InputStream.readFloat(tag, require, default)
函数作用:从数据流读取一个Float(32位,单精度浮点数)类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Float 表示读取变量不成功时的返回值
参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Double 表示读取变量不成功时的返回值

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:Float(Number)
readDouble
函数定义:[stream].InputStream.readDouble(tag, require, default)
函数作用:从数据流读取一个Double(64位,双精度浮点数)类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default Double 表示读取变量不成功时的返回值
参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default String 表示读取变量不成功时的返回值

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:Double(Number)
readString
函数定义:[stream].InputStream.readString(tag, require, default)
函数作用:从数据流读取一个String(UTF8编码)类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
default String 表示读取变量不成功时的返回值
参数 数据类型 描述
tag UInt8 表示欲读取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
TYPE_T 自定义结构体的类型原型 表示该变量的类型原型

对require的说明:
当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回变量的默认值default;
返回数据:String (UTF8编码)
readStruct
函数定义:[stream].InputStream.readStruct(tag, require, TYPE_T)
函数作用:从数据流读取一个自定义结构体类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲读取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
TYPE_T 自定义结构体的类型原型 表示该变量的类型原型
参数 数据类型 描述
tag UInt8 表示欲读取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
TYPE_T [stream].BinBuffer 表示该变量的类型原型

当 require === true 时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false 时,如果当前变量不在数据流中,系统将返回空的结构体实例;
返回数据:自定义结构体的实例
readBytes
函数定义:[stream].InputStream.readBytes(tag, require, TYPE_T)
函数作用:从数据流读取一个[stream].BinBuffer类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲读取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
TYPE_T [stream].BinBuffer 表示该变量的类型原型
参数 数据类型 描述
tag UInt8 表示欲读取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
TYPE_T [stream].List 表示该变量的类型原型

返回数据:[stream].BinBuffer
readList
函数定义:[stream].InputStream.readList(tag, require, TYPE_T)
函数作用:从数据流读取一个[stream],List<T>类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲读取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
TYPE_T [stream].List 表示该变量的类型原型
参数 数据类型 描述
tag UInt8 表示欲读取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
TYPE_T [stream].List 表示该变量的类型原型

对require的说明:
当 require === true时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false时,如果当前变量不在数据流中,系统将返回一个空的[stream].List(T)的实例;
返回数据:[stream].List(T)
readList
函数定义:[stream].InputStream.readList(tag, require, TYPE_T)
函数作用:从数据流读取一个[stream],List<T>类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲读取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
TYPE_T [stream].List 表示该变量的类型原型
参数 数据类型 描述
tag UInt8 表示欲读取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
TYPE_T [stream].Map(T, V) 表示该变量的类型原型

对require的说明:
当 require === true时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false时,如果当前变量不在数据流中,系统将返回一个空的[stream].List(T)的实例;
返回数据:[stream].List(T)
readMap
函数定义:[stream].InputStream.readMap(tag, require, TYPE_T)
函数作用:从数据流读取一个[stream],Map<T, V>类型的数值
输入参数:

参数 数据类型 描述
tag UInt8 表示欲读取变量的数字标识,取值范围[0, 255]
require Boolean 表示当前变量是否为必须值,取值范围{false, true}
TYPE_T [stream].Map(T, V) 表示该变量的类型原型

对require的说明:
当 require === true时,如果当前变量不在数据流中,系统将抛出一个读取数据不存在的异常;
当 require === false时,如果当前变量不在数据流中,系统将返回一个空的[stream].Map(T, V)的实例;
返回数据:[stream].Map(T, V)

registry

TARS 框架中的主控请求模块

包括:
EndpointF.tars
EndpointFTars.js
QueryF.tars
QueryFProxy.js
index.js
package.json

utils

TARS框架辅助工具集合

@tars/utils

TARS框架辅助工具集合

Installation
$ npm install @tars/utils

01.配置文件解析器

var Config = require('@tars/utils').Config;
API

parseFile(sFilePath, [encoding, callback])
解析指定文件

  • sFilePath:文件名
  • encoding:文件编码类型。(默认值: utf8)
  • callback:回调函数,回调函数的格式为function callback(ret, config){},其中ret为对象{code:返回码,成功为0,失败为-1,message:描述,exception:如果成功为undefined,如果失败为事件对象},config为解析器本身
parseText(sText)

解析字符串,并将解析的结果存于内部_data属性中,可以通过get方法获取相应的值。

  • sText:字符串
  • return:true:解析成功,false:解析失败
get(key, defaultValue)

文件被解析之后,会将结果存储在一个对象中,通过get方法可以获取指定的值。注:如果配置文件/字符串中有相同的key,则get获取key对应的值时,不会获取所有的值,而是获取该key最后对应的那个值,可以理解为对应相同的key后面的值覆盖前面的值。

  • key: 需要取值的key值,格式为x1.x2.x3,其中x1、x2、x3依次为深层次的key,注:如果key值本身为x1.x2格式,取该key对应的值时需要写成<x1.x2>,具体使用参见例子。
  • defaultValue: 取不到结果的默认值
    getDomain(key, defaultValue)
    获取key对应的值中类型为Object的属性数组
  • key: key值
  • defaultValue: 取不到结果的默认值
    **getDomainValue(key, defaultValue)
    获取key对应的值中类型为Object的属性值数组
  • key: key值。
  • defaultValue: 取不到结果的默认值
    **getDomainLine(key, defaultValue)
    获取key对于路径下的所有非空行
  • key: key值
  • defaultValue: 取不到结果的默认值
  • return: 数组
    data
    通过该属性,可以获取文件解析的结果
example
var Config = require('@tars/utils').Config;

var config = new Config();
config.parseFile('./config.conf', 'utf8');

var data = config.data;
console.log('data: ', data);
console.log('get: tars.application.server.local: ', config.get('tars.application.server.local'));
console.log('getDomain: tars.application.server: ', config.getDomain('tars.application.server'));
console.log('getDomainValue: tars.application.server: ', config.getDomainValue('tars.application.server'));

具体例子参加examples目录下的test-config.js文件

02.Endpoint工具

var Endpoint = require('@tars/utils').Endpoint;
API

Class方法:parse(desc)
从字符串中解析出Endpoint信息

  • desc:字符串,例如:'tcp -h 127.0.0.1 -p 10000 -t 60000'
  • return:返回Endpoint实例。
    toString()
    Endpoint信息转化成字符串
    copy()
    拷贝Endpoint实例
example
var Endpoint = require('@tars.utils').Endpoint;

var endpoint = Endpoint.parse('tcp -h 127.0.0.1 -p 10000 -t 60000');
console.log('endpoint: ' + endpoint.toString());
console.log('endpoint.copy: ' + endpoint.copy().toString());

具体例子参见examples目录下的test-endpoint.js文件

03.timeProvider工具

var timeProvider = require('@tars/utils').timeProvider;
API

nowTimestamp()
采用Date.now()的方式获取时间,此种方式效率最高,Date.now()方式的效率大概是new Date.getTime()的2倍,是process.hrtime()方式的4倍。

  • return:返回对象
{
  hrtime: //  数据类型。[秒,纳秒],
  timestamp:  //  单位ms
}

diff(oTime)
当前时间相对于oTime的时间间隔

  • oTime:相对时间,nowTimestamp函数返回的对象类型
  • return:浮点类型,时间间隔,单位毫秒
  • 注:nowTimestamp和diff配对使用
    dateTimestamp()
    获取当前的时间戳,即机器从启动到当前的时间(process.hrtime)
  • return:返回对象
{
    hrtime: // 数组类型,[秒, 纳秒],
    timestamp: // 单位ms
}

dateTimestampDiff(oTime)
当前时间相对于oTime的时间间隔

  • oTime:相对时间,dateTimestamp函数返回的对象类型
  • return:浮点类型,时间间隔,单位毫秒
  • 注:dateTimestamp和dateTimestampDiff配对使用
example
var timeProvider = require('@tars/utils').timeProvider;

var i = 0, count = 10000000;
var tt1, tt2, interval = 0;
var t1 = new Date().getTime();
var t2 = t1;

tt1 = timeProvider.nowTimestamp();
for(i = 0; i < count; i++) {
  tt2 = timeProvider.dff(tt1);
}
t2 = new Date().getTime();
console.log('【hrTime】interval: ' + (t2 - t1));

t1 = new Date().getTime();
tt1 = timeProvider.dateTimestamp();
for(i = 0; i < count; i++) {
  tt2 = timeProvider.dateTimestampDiff(tt1);
}
t2 = new Date().getTime();
console.log('【hrTime】interval: ' + (t2 - t1));

具体例子参见examples目录下的test-timer.js文件

03.Promise库
var Promise = require('@tars/utils').promise;

为TARS应用提供一个方便统一的Promise库。开发TARS应用时推荐大家使用此库而不是自己选择Promise库,当出现更好的promise方案时,我们可以直接替换此模块中的实现,直接对所有应用生效。

var Promise = require('@tars/utils').Promise;
var promise = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve(666)
  }, 3000);
});
promise.then(function(data) {
  console.log(data);
});

目前TARS中的Promise是基于bluebird库实现的,在q、bluebird、原生Promise中bluebird性能最好。

winston-tars

基于 winston 的扩展,以提供符合 TARS 框架的日志格式与输出

@tars/winston-tars

@tars/winston-tars提供基于winston的TARS扩展,以提供符合TARS框架的日志格式与输出,在@tars/winston-tars中提供3种transport对象:

  • TarsBase: 提供符合TARS日志的基础类
  • TarsRotate: 提供按大小输出的滚动日志
  • TarsDate: 提供按日期输出的日志
    并提供一种自定义日志级别:
  • TarsConfig: 提供符合TARS框架标准的日志级别和颜色值
    以及相关的辅助方法:
  • Formatter: 提供了符合TARS日志格式标准的内容格式化方法
  • Date-Format: 定义了与时间相关日志滚动的处理方法
    请注意:如果您的服务在TARS平台上运行,应直接使用@tars/logs模块,更为便捷
安装
npm install @tars/winston-tars
使用
var winston = require('winston');

//  Require `@tars/winston-tars` will expose

//  transports
//  `winston.transport.TarsRotate`  
//  `winston.transport.TatsDate`  

//  config
//  `winston.config.tars.levels`
//  `winston.config.tars.colors`
require('@tars/winston-tars');
日志格式

对于支持formatter的transport对象,@tars/winston-tars提供2种方法格式化日志内容:

  • 详细日志:Formatter.Detail([options])
  • 精简日志:Formatter.Simple([options])
    options
  • separ: 日志内容项与项之间的分隔符,默认为|
详细日志:
var winston = require('winston');
var winstonTars = require('@tars/winston-tars');

var logger = new (winston.Logger) ({
  transports: [
    new (winston.transports.Console)({
      formatter: winstonTars.Formatter.Detail();
    })
  ]
});

输出日志的格式为:日期 时间|PID|日志级别|文件名与行号|内容
其中:文件名与行号 部分可选(详见Metadata节)

精简日志
var winston = require('winston');
var winstonTars = require('@tars/winston-tars');

var logger = new (winston.Logger) ({
  transports: [
    new (winston.transports.Console)({
      formatter: winstonTars.Formatter.Simple();
    })
  ]
});

输出日志的格式为:日期 时间|内容

TarsConfig

winston.config.tars提供了符合TARS框架标准的日志级别(levels)与颜色(colors)
TARS框架的日志级别从低到高(及其对应的颜色)为:

  • info: white
  • debug: cyan
  • warn: yellow
  • error: red
  • none: grey
    在使用时需要主动引入:
logger.setLevels(winston.config.tars.levels);
winston.addColors(winston.config.tars.colors);
TarsBase

此模块可单独使用,也可作为其他日志模块的基类。
模块实现了与现有TARS日志类似的管理方式:

  • 定时重新打开日志文件,以便获取fd的变更。(当用户删除/移动当前文件后,模块会自动创建文件)
  • 在文件打开的过程中,产生的日志将会写入临时内存队列,等待文件打开完成后一次性写入。(只要不超过队列最大长度,所有日志均会写入文件)
  • 此模块使用文件名options.filename作缓存,所以使用相同的options.filename多次实例化此模块仅会产生一个单例(共享一个实例)
独立使用
winston.add(winston.transports.TarBase, options)

options:

  • filename: 输出的文件名
  • interval: 重新打开日志文件的间隔,默认值为5000ms
  • bufferSize: 临时队列的最大长度(单位为 条),默认值为10000条
  • prefix: 每条日志内容前缀,默认值为空
  • formatter:定义日志内容格式化方法,默认值为Formatter.Detail()
作为其他类的基类

需要重写如下2个对象:

  • TarsBase.prototype.name: Transport 模块名
  • TarsBase.prototype._checkfile(callback):当重新打开日志文件时会调用此函数,函数处理完成之后,应显式调用callback([err])
    例如:
var TarsFile = function (options) {
  var instance = TarsBase.call(this, options);

  //  由于父类存在缓存,所以这里需判断父类是否有返回值,如果存在(命中缓存)则则直接返回无需继续初始化

  if (instance) {
    return instance;
  }

  //  业务代码
};
util.inherits(TarsFile, TarsBase);

TarsFile.prototype.name = 'TarsFile';

TarsFile.prototype._checkfile = function(cb) {
  //  业务代码
  cb();
};

winston.transports.TarsFile = TarsFile;
TarsRotate

此模块继承于TarsBase
提供按文件大小输出的滚动日志
当到达设定的最大大小时,会自动向下滚动,并创建一个新的日志文件。
例如:app.log文件写到最大大小,则将会执行如下过程:

delete app_n.log
app_n-1.log ===> app_n.log
... ...
app_1.log ===> app_2.log
app.log ===> app_1.log
create app.log
 winston.add(winston.transports.TarsRotate, options)

options:

  • filename: 输出的文件名
  • maxFiles: 最大的文件总数(也就是例子中的n),默认值为10
  • maxSize: 单文件最大大小(单位为bytes),默认值为10M
  • concatStr: 日志文件名中字符间的连接符,默认值为_
  • formatter: 定义日志内容格式化方法,默认值为Formatter.Detai()
    TarsRotate.Master
    如果业务脚本通过Cluster(多进程方式启动的):Worker则只负责写入文件,而移动文件由Master完成,以剞劂多进程资源竞争问题。
    当服务进程(Worker)打开日志文件准备写入时,会向主进程发送消息,如下:
{
  cmd : 'log:rotate',
  msg : {
    filename : String,  // 文件名
    interval : Number,  // 多久打开一回文件
    maxFiles : Number,  // 最大文件数
    maxSize : Number, // 最大单个文件大小
    concatStr : string  // 日志文件名中间字符的连接符
  }
}

主进程(Master)收到消息后,需透传给TarsRotate。Master.start方法,完整的例子如下:

worker.on('message', function(msg) {
  if (msg && typeof msg == 'object' && msg.cmd == 'log:rotate') {
    var data = msg.msg;
    TarsRotate.Master.start(data.filename, data.interval, data.maxFiles, data.maxSize, data.concatStr);
  }
});

process.on('exit', function() {
  TarsRotate.Master.close();
});
···
**如果服务通过node-agent(或在TARS平台)运行,则无需配置平台运行,无需显式调用此模块。只需按照平时的写法console.[log|info|warn|error]即可正确的输出滚动日志**
######DateFormat
定义了与时间相关的日志(TarsDate)滚动的处理方法:
+ 按1天日志:LogByDay([interval, pattern])
  + **interval**: 1 每隔一天滚动一份日志
  + **pattern**: %Y%m%d 年月日(如:20150101)
+ 按1小时日志:LogByHour([interval, pattern])
  + **interval**: 1 每隔一小时滚动一份日志
  + **pattern**: %Y%m%d 年月日(如:2015010110)
+ 按10分钟日志:LogByHour([interval, pattern])
  + **interval**:每隔十分钟滚动一份日志
  + **pattern**:%Y%m%d%H%M 年月日小时分钟(如:201501011020)
+ 自定义格式日志:LogByCustom(pattern)
  + **pattern**:需要用户自定义
其中pattern为日志文件名中的时间格式,可参见 linux strftime
######例子
每隔1天滚动一份日志
```javascript
DateFormat.LogByDay
或者
new DateFormat.LogByDay()

每隔20分钟滚动一份日志

new DateFormat.LogByMinute(20)

每隔20分钟滚动一份日志,文件名中时间格式为%Y-%m-%d_%H:%M
···javascript
new DateFormat.LogByMinute(20, %Y-%m-%d_%H:%M)

######TarsDate
此模块继承于TarsBase
提供按日期(年、月、日、小时、分)输出的日志
当到达设定的时间间隔时,会自动创建一个新的日志文件
输出的文件名的格式为:filename_[%Y|%m|%d|%H|%M].log,
如:app_20141015.log
```javascript
winston.add(winston.transports.TarsDate, options)

options:

  • filename:输出的文件名
  • concatStr:日志文件名中字符间的连接符,默认值为_
  • format:创建新文件的间隔,为DateFormat对象,默认值为FORMAT.LogByDay
  • formatter:定义日志内容格式化方法,默认值为Formatter.Simple()
    为了方便使用TarsDate.FORMAT = DateFormat
Metadata

通过指定Metadata可输出2种附加数据。
pid
通过pid属性,可以指定日志输出条目中进程id部分,默认情况下为process.pid
如:

logger.info('data', {
  pid : 123456
});

则输出:
2015-01-01 00:00:01|123456|INFO|data
lineno
通过lineno属性,可以指定日志输出条目中文件名与行号部分,默认值为空(也就是不输出这一节)。
如:

logger.info('data', {
  lineno : 'app.js:6'
});

则输出:
2015-01-01 00:00:01|123456|INFO|app.js:6|data

notify

TARS 框架中用于业务的(告警)消息上报

@tars/notify

上报业务(框架)消息(告警)至TARS平台。

report(message[, id])

将消息上报到平台上,并可以在WEB管理页面上查看到。

  • message: 消息内容(必填)
  • id: 服务线程(进程)ID,默认值为process.id
notify(message[, level, id])

上报通知信息到平台。

  • message: 通知内容(必填)
  • level: 通知内容的级别,为LEVEL枚举,默认值为LEVEL.NOTIFYNORMAL
  • id: 服务线程(进程)ID,默认值为process.pid
    LEVEL 枚举中有3项可选:
  • LEVEL.NOTIFYNORMAL:正常(默认)
  • LEVEL.NOTIFYWARN:警告
  • LEVEL.NOTIFYERROR:错误
    如果level的级别为LEVEL.NOTIFYERROR,则会进行短信告警,平台每10分钟对上报的异常进行收敛告警。

logs

TARS 框架规范的日志组件,包含滚动(大小、时间)与染色日志

@tars/logs

基于winston与winston-tars开发,符合TARS框架规范的日志组件,其中包含染色日志、滚动(大小、时间)日志。

安装
npm install @tars/logs
例子

输出滚动日志

var logger = new tarsLogs('TarsRotate');

输出名为access的按天日志

var logger = new tarsLogs('TarsDate', 'access');

输出名为access的按小时日志

var logger = new tarsLogs('TarsDate', 'access', {
  format : tarsLogs.DateFormat.LogByHour
});

输出名为access的按20分钟滚动的日志,文件名为2015-01-01_10:00这样的格式

var logger = new tarsLogs('TarsDate', 'access', {
  format: new (tarsLogs.Dateformat.LogByMinute)(20, '%Y-%m-%d_%H:%M')
});

指定输出INFO级别的日志信息

logger.info('data');
logger.info('data1', 'data2', 'data3');

指定当前INFO级别的日志输出需要染色

logger.info('data', logger.getDyeingObj(true));

初始化

如果服务在TARS平台上运行,则无需配置此项
如果服务在本地环境中调试,所有日志类型都讲输出至Console

可以通过调用tarsLogs.setConfig(data)静态方法进行初始化
data(String|Object)可作为tars配置文件路径或已配置的(@tars/utils).Config实例。

使用

实例化
var tarsLogs = require('@tars/logs');
var logger = new tarsLogs(type, [name, options]);

type(String)日志类型:

  • TarsRotate: 按大小滚动日志
  • TarsDate: 按时间滚动日志
    name(String)用户自定义的文件名(可不填)
    options(Object)根据不同的日志类型,存在不同的参数,但下面的参数是各类型共享的:
  • hasSufix: 日志文件名是否带.log后缀,默认值为true
  • hasAppNamePrefix: 是否允许框架在日志文件名上增加业务相关的标识,默认值为true
  • concatStr: 日志文件名中字符间的连接符,默认值为_
  • separ: 日子内容项之间的分隔符,默认值为|
    options中的其它参数,请详简不同日志类型的说明项。
    在正常情况下,相同一个日志文件应该共享相同的一个logger,而不是多次进行实例化

日志输出

在TARS中存在4种日志级别INFO、DEBUG、WARN、ERROR可用对应的方法进行输出

logger.info([data], [...]);
logger.debug([data], [...]);
logger.warn([data], [...]);
logger.error([data], [...]);

方法支持多重参数,详情可见util.format()
如需要染色,详见染色节

文件名与行号

默认情况下,输出的日志中包含调用输出方法的代码所在的 文件名:行号。
可通过如下示例进行自定义(用于封装模块)或者关闭(以提高性能)。
关闭文件名与行号输出:

logger.info('data', {
  lineno : false
});

自定义文件名与行号输出:

logger.info('data', {
  lineno : 'app.js:123'
});

更多详细信息,请参考@tars/winston-tars.Metadata

日志级别

日志级别的优先级为:INFO < DEBUG < WARN < ERROR < NONE
其中,除了按大小滚动日志(TarsRotate)默认级别为DEBUG,其他均为INFO。
如需变更日志级别,可调用logger.setLevel(level)方法,传入需要的日志级别即可:

logger.setLevel('info');
logger.setLevel('none');  // none为一种特殊的日志级别,所有日志均不输出

如果服务在TARS平台上运行:

  • 模块会接收日志级别变更的管理命令,自动变更当前日志级别。
  • 模块会读取TARS配置文件中tars.application.server.logLevel节,以配置日志级别。
  • 以上两项日志级别的配置仅针对按大小滚动日志(TarsRotate)生效。
日志内容格式(Formatter)

模块提供精简Formatter.Simple()与复杂Formatter.Detail()两种日志处理方法:

  • 复杂:日期 时间|PID|日志级别|文件名与行号|内容
  • 精简:日期 时间|内容
    默认情况下,不同类型的日志会使用不同的处理方法。
    关于Formatter的详情,请访问@tars/winston-tars.Formatter
    #######按大小滚动日志(TarsRotate)
    在初始化类型为TarsRotate的logger时,options还接受如下参数:
  • maxFiles: 最大的文件总数(也就是例子中的n),默认值为10
  • maxSize: 单文件最大大小(单位为bytes),默认值为10M
  • formatter: 定义日志内容格式化方法,默认值为Formatter.Detail()
    关于TarsRotate的详情,请访问@tars/winston-tars.TarsRotate
时间相关(DateFormat)

定义了与时间相关的日志(TarsDate)滚动的处理方法:

  • 按1天日志:LogByDay([interval, pattern])
  • 按1小时日志:LogByHour([interval, pattern])
  • 按10分钟日志:LogByMinute([interval, pattern])
  • 自定义格式日志:LogByCustom(pattern)
    其中,interval为日志滚动间隔,pattern为日志文件名中时间的格式
    一般情况下,可直接使用字面量:
var logger = new tarsLogs('TarsDate', 'access', {
  format : tarsLogs.DateFormat.LogByDay 
});

但如果需要自定义间隔或日志文件名,则需要实例化:

var logger = new tarsLogs('TarsDate', 'access', {
  format : new tarsLogs.DateFormat.LogByDay(3, '%Y-%m-%d');
});

关于DateFormat的详情,请访问@tars/winston-tars.DateFormat

按时间滚动日志(TarsDate)

在初始化类型为TarsDate的logger时,options还接受如下参数:

  • format:创建新文件的间隔,为DateFormat对象,默认值为FORMAT.LogByDay
  • formatter:定义日志格式化方法,默认值为Formatter.Simple()
    关于TarsDate的详情,请访问:@tars/winston-tars.TarsDate

染色

在每一条日志写入时可以制定是否需要对此日志进行染色(也就是说,染色的开关并不在初始化时而在日志写入时)。
仅有按大小滚动(TarsRotate)与按时间滚动(TarsDate)输出的日志可以进行染色。
染色的日志不仅按照之前逻辑进行输出,也会按照相同的逻辑会多输出一份放在$LOG_PATH$/tars_dyeing/目录下。
染色日志总是全量输出(忽略当前日志级别进行输出)。

染色对象

染色对象标识了当前染色的状态(是否需要染色及附加信息)。
染色对象需要通过@tars/dyeing提供的方法生成。
为了便于使用,本模块封装了染色对象的生成方式。可通过logger.getDyeingObj()来获取染色对象。
打开染色:

logger.getDyeingObj(true);

打开染色并设置染色的val为guid,ket为guid|sn

logger.getDyeingObj(true, 'guid', 'guid|sn');

实际应用中,不应调用此方法生成染色对象,而应直接使用其他模块提供的染色对象
符合染色标准的其它模块,均会提供Object.getDyeingObj()方法,可通过调用它获取染色对象,而不是使用此模块的方法。
关于染色的详细信息,请访问@tars/dyeing获取。

使用

需要对某条日志进行染色时,需在调用logger特定方法(日志级别)的最后一个参数传入染色对象。
输出日志内容为data1 data2并强制染色的日志

logger.info('data1', 'data2', logger.getDyeingObj(true));

输出日志内容为data并根据rpc.server调用链上的染色信息进行染色的日志

tars.TestImp.prototype.echo = function (current, i) {
  logger.info('data', current.getDyeingObj());
}

rpc具体获取染色对象的方式,请详见@tars/rpc

config

TARS 框架中用于获取服务配置文件

@tars/config

安装
npm install @tars/config
使用
var tarsConfigHelper = require("@tars/config");
var tarsConfig = new tarsConfigHelper(options);
options

如果服务通过node-agent(或在TARS平台)运行,则无需填写。

  • app:tars.app名称
  • server:服务名称

方法调用

事先说明,以下几个方法调用,configOptions可以是一个object,包含以下参数。

struct ConfigInfo {
  //  业务名称 0
  require string appname;
  //  服务名称 1
  require string servername;
  //  配置文件名称 2
  require string filename;
  //  是否只获取应用配置,默认为false,如果为true则servername可以为空 3
  require bool bAppOnly = false;
  //  服务所在节点(ip) 4
  optional string host;
  //  set分组名称 5
  optional string setdivision;
};

所有的放大调用均返回[Promise]对象。

loadConfig(configOptions[, configFormat])

获取配置文件内容
configOptions:{string/object} tars配置文件名或一个Object(见上面configOptions说明)configFormat:转换格式,如果不转,每个配置项直接返回字符串。"json:"自动转为json格式,"c":c++格式的配置项

tarsConfig.loadConfig("Server.conf").then(function (data) {
  console.log("loadConfig back", data);
}, function (err) {
  console.log("loadConfig err", err);
});
getConfigList(configOptions)

获取配置文件列表
configOptions:{string/object} tars配置文件名或者一个Object(见上面configOptions说明),也可以不传,那么就直接使用默认的app和server。

tarsConfig.getConfigList().then(function(configList) {
  console.log("examples:getConfigList back", configList);
},
function(err) {
  console.error("getConfigList error", err);
});
getAllConfigData([configOptions, configFormat])

获取配置文件列表以及配置项的内容
configOptions:{string/object} tars配置文件名或者一个Object(见上面configOptions说明),可缺省。configFormat:{string}转换格式,默认配置项直接返回字符串。"json:"自动转为json格式,"c":c++格式的配置项
返回的对象内,key:文件名,value:文件内容

tarsConfig.getAllConfigData().then(function(configDatas) {
  console.log("examples:getAllConfigData back", configDatas);
}, 
function(err) {
  console.error("getAllConfigData error", err);
});

setTimeout(iTimeout)
设置调用超时时间,默认是30s,参数单位为ms
loadServerObject(configFormat)
加载服务默认的配置项,并且转换为json
configFormat: {string} "c": c++ 格式的配置项(默认值),"json": json格式配置项返回的对象内,key:文件名,value:文件内容

tarsConfig.loadServerObject().then(function() {
  console.log("serverObject", serverObject);
});

事件支持

configPushed

从TARS配置平台push配置文件的时候,触发此事件

tarsConfig.on("configPushed", function(filename, content) {
  console.log("config pushed", filename, content);
});

dyeing

TARS 染色基础模块

@tars/dyeing

TARS染色基础模块,提供获取与判断染色对象的方法。
使用者:不应该直接使用此模块,而应使用其他符合染色标准的模块获取染色对象
模块开发者:获取TARS染色对象,应使用此模块的gen()方法获取染色对象

染色(简介)

染色是在接口的调用链(服务端<==>客户端)上传递的一种状态,用于标识一次特定的请求处理过程。
这种状态由一个标志位与附加信息KEY(可选)构成。
调用链上的服务可以设置(发起)与读取(接收)这种状态,并作对应的处理(如输出对应的日志、特性)。
在系统中,染色由此模块与其对应的约定构成。

标准(约定)

如模块使用染色体系,则需遵守如下约定(与此同时,我们将符合约定的模块称之为符合染色标准的模块)

  1. 提供getDyeingObj()方法,返回染色对象。
  2. 通过@tars/dyeing.gen()方法,生成染色对象。
  3. 通过@tars/dyeing.is()方法,判断染色对象是否有效。
模块方法

TarsDyeing.gen(dyeing[, key, args])
通过调用此方法,可获得染色对象:
dyeing:是否需要染色
key:染色添加附加信息KEY(可选)
args:程序的附加参数(可选)
key会通过调用链传递给下一个服务,而args仅只会在当前的染色对象中有效(并不会进行传递)
请注意:染色对象与染色发起原因,这是不同的(东西)。此模块仅关心染色对象,而不关心染色发起原因。
TarsDyeing.is(obj)
调用此方法,可以判断一个传入的对象是否是染色对象。
如传入空对象,方法同样返回false

染色对象 属性

dyeingObj.dyeing
是否需要染色,此为Boolean
dyeingObj.key
染色传递的附加信息,此为String且可选值,不一定存在
dyeingObj.args
染色对象本身的附加信息,业务代码不应将其在服务间进行传递(仅作为本地参数使用)。此对象类型不限,同时也为可选值

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容