Tars 是将腾讯内部使用的微服务架构TAF(Total Application Framework)多年的实践成果总结而成的开源项目。Nodejs其js的语法对json处理的优势,可以适用于快速实现轻量级接口。
Tars-nodejs随着Tars开源之后与2018中旬一期发布,开启了Tars对于nodejs的支持。
下面以tarsnode整合腾讯云短信sdk为例,带大家掌握如何快速实现tarsnodejs的服务端与客户端。
module QCloudSms
{
struct HeadReq
{
0 require string requestId;//调用请求ID
};
//短信发送
struct SendSmsReq
{
0 require string phoneNumber; // 短信接收方手机号
1 require int templateId; // 短信模板ID
2 require vector<string> params; //对应短信模板的数组值
};
//短信标准回包
struct SendSmsRsp
{
0 require int result;//错误码,0 表示成功(计费依据),非 0 表示失败
1 require string errmsg;//错误消息,result 非 0 时的具体错误信息
2 require string ext;//用户的 session 内容,腾讯 server 回包中会原样返回
3 require string sid;//本次发送标识 id,标识一次短信下发记录
4 require int fee;//短信计费的条数
};
//请求短信验证码
struct SendSmsCodeReq
{
0 require string phoneNumber; // 短信接收方手机号
1 require int duration ; // 过期时间单位分钟
};
//验证短信验证码
struct SmsCodeCheckReq
{
0 require string phoneNumber; // 短信接收方手机号
1 require string vcode ; // 验证码
};
//短信验证码消息回复
struct SendSmsCodeRsp
{
0 require int code; // 消息码
1 require string msg; // 字符串内容
2 require string data; // 具体内容
};
interface SmsTars
{
// 发送单条基于模板的短信
int sendSingleSmsWithTpl(HeadReq head ,SendSmsReq req, out SendSmsRsp rsp);
//发送短信验证码
int sendSmsCode(HeadReq head ,SendSmsCodeReq req, out SendSmsCodeRsp rsp);
//验证短信验证码
int checkSmsCode(HeadReq head ,SmsCodeCheckReq req, out SendSmsCodeRsp rsp);
};
};
可以看到tars协议风格与protobuf比较类似,struct代表结构体类似pb的message,interface类似pb的service
使用tars2node将Tars IDL 定义文件转换为 JavaScript 语言所使用的版本,一般同时生成客户端及服务端,可以获得三个文件:QSms.js、QSmsImpl.js、QSmsProxy.js
QCloudSms.SmsTarsImp.prototype.sendSingleSmsWithTpl = async (current, head, req, rsp) => {
//TODO
}
QCloudSms.SmsTarsImp.prototype.sendSmsCode = async (current, head, req, rsp) => {
//TODO
}
QCloudSms.SmsTarsImp.prototype.checkSmsCode = async (current, head, req, rsp) => {
//TODO
}
接下来只需要继续完善 QSmsImpl.js 实现文件中具体的函数就可以了。
app
|--config(配置文件)
|--tars(tars协议及实现)
|--utils
main.js(入口)
package.json
在实现之前,我们先规划一下整个服务的代码结构。作为工具类微服务,我个人主张精简小巧,就像一个函数一样一目了然
QCloudSms.SmsTarsImp.prototype.sendSingleSmsWithTpl = async (current, head, req, rsp) => {
const logger = new tarsLogs('TarsDate', 'sendSingleSmsWithTpl');
const requestId = head.toObject().requestId;
const { phoneNumber, templateId, params } = req.toObject();
logger.debug(requestId, 'begin>>>', phoneNumber, templateId, params);
let rst = {};
if (!verifUtil.mobileVer(phoneNumber)) {
rst = {
result: 1,
errmsg: 'invalid phoneNumber',
ext: '',
sid: '',
fee: 0
}
rsp.readFromObject(rst);//将对象转为
return current.sendResponse(rst.result, rsp);
}
try {
rst = await smsUtils.sendSingleSms(phoneNumber, templateId, params);
logger.debug(requestId, 'rst<<<', rst);//{"result":0,"errmsg":"OK","ext":"","sid":"8:MRasIKfs6eMBxstTmN020180910","fee":1}
//错误码从1001~1036目前
} catch (err) {
logger.error(requestId, 'err<<<', err);
rst = {
result: -1,
errmsg: 'tars server error see details in sendSingleSmsWithTpl.log',
ext: '',
sid: '',
fee: 0
}
}
rsp.readFromObject(rst);
return current.sendResponse(rst.result, rsp);
};
函数从current之后开始,与interface中对应的参数保持一致
入参的.toObject()方法可以帮助你将内容快速转为和struct一致的json对象,清理掉其他无用的描述
出参的.readFromObject()可以帮助你将一个json内容复制进来
发送短信的具体实现被封装在了smsUtils工具类中,它主要集成了腾讯云短信nodejs版的SDK包,这里不再赘述。
'use strict';
const tarsLogs = require('@tars/logs');//日志
const logger = new tarsLogs('TarsDate', 'main');
const TARS = require("@tars/rpc");//RPC服务
const QCloudSms = require("./app/tars/svr/QSmsImp").QCloudSms;
const TarsConfigHepler = require('@tars/config');//加载外部配置服务
let smsConfig = require('./app/config/smsConfig');
let redisConfig = require('./app/config/redisConfig');
if (process.env.TARS\_CONFIG) {
let tarsConfig = new TarsConfigHepler(process.env.TARS\_CONFIG);
logger.info('tars server is starting...');
TARS.server.getServant(process.env.TARS\_CONFIG).forEach(config => {
let map, svr;
map = {
"demo.SmsSvr.SmsSvrObj": QCloudSms.SmsTarsImp,
};
if (map[config.servant]) {
logger.info("Start Servant..." + config.servant);
svr = TARS.server.createServer(map[config.servant]);
svr.start(config);
logger.info('tars server is started');
} else {
logger.info("Servant Not Exist..." + config.servant);
}
});
//配置文件加载及更新
tarsConfig.loadConfig('SmsSvr.conf', { format: tarsConfig.FORMAT.JSON }).then(data => {
logger.info("data:", data);
smsConfig.init = data;
}, (err) => {
logger.error("loadConfig SmsSvr err", err.response || err);
});
//配置文件加载及更新
tarsConfig.loadConfig('redis.conf', { format: tarsConfig.FORMAT.JSON }).then(data => {
logger.info("redis data:", data);
redisConfig.init = data;
}, (err) => {
logger.error("loadConfig redis err", err.response || err);
});
tarsConfig.on('configPushed', (file) => {
logger.info('configPushed', file);
if (file === 'SmsSvr.conf') {
tarsConfig.loadConfig(file, { format: tarsConfig.FORMAT.JSON }).then(data => {
logger.info("SmsSvr data:", data);
smsConfig.init = data;
}, (err) => {
logger.error("configPushed SmsSvr err", err.response || err);
});
}
if (file === 'redis.conf') {
tarsConfig.loadConfig(file, { format: tarsConfig.FORMAT.JSON }).then(data => {
logger.info("redis data:", data);
redisConfig.init = data;
}, (err) => {
logger.error("configPushed redis err", err.response || err);
});
}
});
} else {
logger.error('is not a online tars server');
}
作为服务端主入口,main.js判断了当前的环境是否是tars环境,并在环境变量中读取当前服务的的配置文件,尝试启动名为demo.SmsSvr.SmsSvrObj的Servant,并从当前这个服务中拉取SmsSvr.conf的服务配置,确保服务在执行过程中可以动态的替换配置参数,实现服务的灵活可配。
const tarsLogs = require('@tars/logs');
const logger = new tarsLogs('TarsRotate','QcloudSmsUtils');//滚动调试日志
const Tars = require("@tars/rpc").client;
const QCloudSms = require("../tars\_proxy/QSmsProxy.js").QCloudSms;
const prx = Tars.stringToProxy(QCloudSms.SmsTarsProxy, "demo.SmsSvr.SmsSvrObj");
class QcloudSmsUtil {
static async sendSingleSms(req, phoneNumber, templateId, params) {
const requestId = (req && req.id) ? req.id : 'NoRequestId';
logger.debug(requestId, 'sendSingleSms>>>', phoneNumber, templateId, params);
try {
let headReq = new QCloudSms.HeadReq();
let sendSmsReq = new QCloudSms.SendSmsReq();
headReq.requestId = requestId;
sendSmsReq.readFromObject({
phoneNumber: phoneNumber,
templateId: templateId,
params: params
})
let result = await prx.sendSingleSmsWithTpl(headReq, sendSmsReq);
let rsp = result.response.arguments.rsp;
logger.info(requestId, 'sendSingleSms<<<', "result.response.costtime:", result.response.costtime);
logger.info(requestId, 'sendSingleSms<<<', "result.response.return:", result.response.return);
logger.debug(requestId, 'sendSingleSms<<<', "result.response.arguments.rsp:", rsp);
return rsp.toObject();
} catch (err) {
logger.debug(requestId, 'sendSingleSms>>>', phoneNumber, templateId, params);
if (err.response) {//来自tars层面的错误
logger.info(requestId, 'sendSingleSms<<<', "error.response.costtime:", err.response.costtime);
logger.info(requestId, 'sendSingleSms<<<', "error.response.error.code:", err.response.error.code);
logger.debug(requestId, 'sendSingleSms<<<', "error.response.error.message:", err.response.error.message);
return Promise.reject(err.response.error.message);
} else {
logger.error(requestId, 'sendSingleSms error<<<', err.message);
return Promise.reject(err.message);//node层面的错误
}
}
}
}
module.exports = QcloudSmsUtil;
客户端声明好对应调用的proxy地址将sendSingleSmsWithTpl函数的两个入参内容传入其中,就可以实现对服务端的调用了,需要注意的是,tars调用的错误内容被存放在error.response中,故在代码里进行了一次判断,如果直接将error抛出是无法得到所想要的错误信息的,还会因为内容太多而容易撑爆磁盘。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。