微服务开发文档

微服务使用JS脚本实现,支持模块化管理、高并发访问、数据库访问,主要用于扩展后端服务,或者创建其他独立的业务逻辑。 可用于实现移动端和服务端访问请求,以及通用的业务处理和数据查询服务,一个视频看懂微服务点击这里查看实战demo。架构图如下所示:

主要应用场景

  1. 扩展冰狐SaaS功能。一般SaaS系统都是通用的,但有时客户根据自己的业务特点可能有少部分功能无法完全满足需求,此时用户就可以通过编写微服务并注入到冰狐SaaS系统中,来满足自己的特殊业务需求。
  2. 实现自己的独立后端需求。有些业务我们必须要使用后端服务,如果用户独立开发一套后端系统,金钱和时间成本会比较高。此时可以直接使用微服务,编写js代码来搞定,不需要单独购买和维护各种服务器,不需要掌握各种复杂的开发和部署工具。

移动端接口

开发者可以在app脚本中通过callMicroService函数来调用微服务。移动端接口可以指定脚本和函数名(默认函数名为main),params直接透传给微服务脚本相应的函数,函数的返回值即为接口返回值。
典型应用场景:当移动端脚本开发者需要调用自定义的后端服务功能时使用callMicroService,比如访问数据库、获取用户和设备相关信息等。

移动端脚本demo

// 调用接口call_demo,参数分别为'name'和123,返回值为ret
function main() {
    var ret = callMicroService('call_demo', ['name', 123]);
    console.log('ret:' + ret);
}
                                        

服务端脚本demo

// 接口call_demo对应的脚本, name和value为移动端传入的参数
function main(name = '', value = 1) {
    console.log('name:' + name + '  value:' + value);
    return name + value;
}
                                        

服务端接口

第三方服务端可以调用open api:/api/call_micro_service来调用微服务。服务端接口可以指定脚本和函数名(默认函数名为main),query参数params直接透传给微服务脚本相应的函数,函数返回值为接口返回值。
典型应用场景:当开发者需要让第三方调用开发者自定义的后端服务功能时使用该接口。

第三方调用demo

// 调用服务端接口call_demo,参数为["name", 123],clientKey和accessToken请替换成自己的鉴权参数
                                        https://aznfz.com/api/call_micro_service?clientKey=xxxxx&accessToken=yyyyy&name=call_demo&isDev=true& params=%5B%22name%22%2C%20123%5D
                                        

服务端脚本demo

// 接口call_demo对应的脚本, name和value为移动端传入的参数
function main(name = '', value = 1) {
    console.log('name:' + name + '  value:' + value);
    return name + value;
}
                                        

业务处理服务

若需要让用户在网页端点击界面执行某个业务,则可以创建业务处理服务,可以让用户输入参数,然后触发云端的微服务(服务参数直接透传给微服务脚本),微服务可以控制所有手机执行任务。
例:录入数据业务就可以通过创建业务处理服务解决,首先用户输入数据并透传给微服务,微服务规则化数据后存入数据库。
支持的参数类型:整数、浮点数、布尔、字符串、文件、选项和设备。选项类型参数支持两种格式默认数据:1.用,分割多个item,每个item用:分割name和value,例:微信:1,支付宝:2。2.请求后端接口获取数据,用json对象表示,例:{"scriptName":"common", "functionName":"getDevice"},系统会自动执行common脚本的getDevice函数并返回结果作为选项的数据项。
典型应用场景:当用户需要从外部录入数据或者需要在网页端触发某个行为时可以使用业务处理服务,实际处理过程由微服务脚本完成,比如上传用户资料,触发设备调度任务等。

业务处理demo

业务参数元数据:
[{"name":"param","id":"p","type":"int","defaultValue":12,"description":"测试"}]

// 业务对应的脚本如下:(注意,函数参数必须与业务参数一致)
function main(p=1) {
    console.log('param:' + p);

    var users = userList();
    console.log('users:' + users);
    for (var u of users) {
        console.log('u:' + u);
        var devices = deviceList(u.openId);
        console.log('用户:' + u.username + ' 设备:' + devices);
    }

    // 获取管理员的设备
    var devices = deviceList('');
    console.log('管理员设备:' + devices);
}
                                            

数据查询服务

若用户需查询数据,则可以创建数据查询服务,以表格的形式显示数据。
数据查询处理函数原型:function main(fetchCountOnly, conditions, startIndex, itemCount){},fetchCountOnly表示是否仅仅获取符合查询条件的数据个数,conditions为用户输入的查询条件,startIndex和itemCount分别表示获取数据的起始游标位置和数据条数;返回数据为对象数组,例:[{"key","value"},{"key","value"}],key与列的id相对应,表格显示key对应的value值
查询参数:指查询数据时用户指定的参数,支持text和select,当执行服务时会让用户输入具体查询参数。text类型:返回的数据对应项包含text才返回,select类型:返回数据对应项等于选择值才返回;
列:指显示数据时的表格列,注意列id要与数据查询处理函数返回数据对象的key一致,否则无法显示;
数据操作:指对数据item的操作,比如删除等。每个数据操作可以指定脚本和函数用于具体处理,处理函数原型为 function main(item){},item为当前操作的数据。
典型应用场景:当用户需要查看系统内部数据时(可以类比于数据报表功能),可以使用数据查询模块来实现,比如查询所有上传的用户资料等。

查询脚本demo

/*
[{
	"id": 1,
	"name": "a",
	"value": "postman"
}, {
	"id": 2,
	"name": "ab",
	"value": "freeman"
}, {
	"id": 3,
	"name": "b",
	"value": "policeman"
}, {
	"id": 4,
	"name": "c",
	"value": "bad guy"
}]
*/
// 用于获取需要显示的表格数据,查询脚本的参数必须是如下格式:fetchCountOnly表示是否仅获取数据总数,conditions表示json数组格式的查询条件,startIndex表示游标值,itemCount表示一页的最大个数
function main(fetchCountOnly, conditions, startIndex, itemCount) {
    var jsonConditions = JSON.parse(conditions);
    var data = JSON.parse(getCustomData('query_data'));
    var ret = data;
    if (jsonConditions && jsonConditions.length > 0) {
        ret = [];
        for (var item of jsonConditions) {
            if (item.name == 'name') {
                for (var d of data) {
                    if (d.name.includes(item.value)) {
                        ret.push(d);
                    }
                }
            }
        }
    }

    if (fetchCountOnly) {
        // 返回符合条件的数据个数
        return ret.length;
    } else {
        // 返回符合条件的格式化数据
        return ret;
    }
}

// 删除数据操作脚本, 脚本的参数必须如下所示,item表示选中后将要操作的数据项
function deleteItem(item) {
    var data = JSON.parse(getCustomData('query_data'));
    var index = 0;
    for (var d of data) {
        if (d.id == item.id) {
            break;
        }
        ++index;
    }
    data.splice(index, 1);
    return setCustomData('query_data', data + '');
}                                            

通用服务

若用户需增加数据、删除数据、修改数据、查询数据、执行某些比较复杂的业务等,则可以创建通用服务。通用服务是基于表格数据然后在表格数据上做相关的一些操作,完全可以替代数据查询服务。
请先点击查看通用服务(请对照下面的demo看) 「视频教程1」 「视频教程2」

查看后端微服务demo源码

function getDevice() {
    var list = deviceList(rsOpenId);
    var ret = [{name: 'te', value: 'uuiiiiid'}, {name: 'ff', value: 'egwgggggg'}];
    for (var item of list) {
        ret.push({name: item.name, value: item.uuid});
    }
    console.log('ret', ret);
    return ret;
}

function confirmAdd(itemIds, params) {
    console.log('add=====> itemIds', itemIds, 'params', params);
    var obj = {};
    for (var p of params) {
        obj[p.name] = p.value
    }
    console.log('obj:' + obj)
    var ret = dbInsert('test', obj);
    console.log('ret:' + ret);
}

function confirmDelete(itemIds, params) {
    console.log('confirm delete', itemIds, params);
    var b = false;
    for (var id of itemIds) {
        b = dbDelete('test', [`id=${id}`]);
    }
    return b;
}

function confirmEdit(itemIds, params) {
    console.log('confirm edit', itemIds, params)
    var sets = [];
    for (var item of params) {
        if (item.name == 'device') {
            item.name = 'uuid';
        }
        var s = item.name + '=';
        if (item.type == 'string') {
            s+='"';
            s += item.value;
            s += '"';
        } else {
            s += item.value;
        }
        sets.push(s);
    }
    var b = false;
    console.log('sets:', sets);
    for (var id of itemIds) {
        b = dbUpdate('test', sets, [`id=${id}`]);
    }
    console.log('b:', b);
    return b;
}

function confirmProcess(itemIds, params) {
    console.log('confirmProcess:' + itemIds + '  params:' + params)
}

function query(fetchCountOnly, conditions, startIndex, itemCount) {
    console.log('fetchCountOnly:' + fetchCountOnly + ' conditions:' + conditions + ' startIndex:' + startIndex + '  itemCount:' + itemCount);
    var params = [];
    for (var item of conditions) {
        if (item.name == 'uuid') {
            params.push(`${item.name}="${item.value}"`);
        }
    }
    console.log('params:' + params);

    if (fetchCountOnly) {
        var qRet = dbQuery('test', 'count(id) as count', params);
        console.log('qRet:' + qRet);
        return qRet[0].count;
    } else {
        var qRet = dbQuery('test', '*', params, '', startIndex, itemCount);
        console.log('qRet:' + qRet);
        return qRet;
    }
}


1.数据查询函数原型:function query(fetchCountOnly, conditions, startIndex, itemCount){},fetchCountOnly表示是否仅仅获取符合查询条件的数据个数;conditions为用户输入的查询条件,比如:[{"name":"device","value":"abc"},{"name":"isDev","value":"true"}] ;startIndex和itemCount分别表示获取数据的起始游标位置和数据条数;返回数据为对象数组,例:[{"id":1, "name":"kate"},{"id":2, "name":"tom"}]。
function query(fetchCountOnly, conditions, startIndex, itemCount) {
    console.log('fetchCountOnly:' + fetchCountOnly + ' conditions:' + conditions + ' startIndex:' + startIndex + '  itemCount:' + itemCount);
    var params = [];
    for (var item of conditions) {
        if (item.name == 'uuid') {
            params.push(`${item.name}="${item.value}"`);
        }
    }
    console.log('params:' + params);

    if (fetchCountOnly) {
        var qRet = dbQuery('test', 'count(id) as count', params);
        console.log('qRet:' + qRet);
        return qRet[0].count;
    } else {
        var qRet = dbQuery('test', '*', params, '', startIndex, itemCount);
        console.log('qRet:' + qRet);
        return qRet;
    }
}

2.查询参数:指查询数据时用户指定的参数,组成查询条件(1中的conditions),支持字符串、整数、布尔、浮点、选项等,当执行服务时会让用户输入具体查询参数。text类型:返回的数据对应项包含text才返回,选项类型:返回数据对应项等于选择值才返回;重点介绍下选项类型(select),默认值主要支持两种方式:1)直接硬编码,name1:value1,name2:value2 ,name为显示值,value为实际值。比如“微信:1,支付宝:2”;2)通过后端微服务获取,{"functionName":"getDevice"},这里的functionName为后端微服务脚本中的函数名。如果有多个查询条件,并且多个条件间有关联,则需要使用next和previous来指定查询参数的id,{"functionName":"getDevice", "next":"account"} {"functionName":"getAccount","previous": "device"},表示device参数的值会影响(决定)account参数
function getDevice() {
    var list = deviceList(rsOpenId);
    var ret = [];
    for (var item of list) {
        ret.push({name: item.name, value: item.uuid});
    }
    console.log('ret', ret);
    return ret;
}

// 这里的uuid为device中选择设备的uuid
function getAccount(uuid) {
    console.log('getAccount:', uuid);
    var ret = [];
    var arr = [];
    if (strIsNotEmpty(uuid)) {
        arr = dbQuery('test', '*', [`uuid='${uuid}'`]);
    } else {
        ret.push({name:'全部', value: 0});
        arr = dbQuery('test', '*', [`id>0`]);
    }
    for (var item of arr) {
        ret.push({name: item.account, value: item.id})
    }
    console.log('arr:', arr);
    return ret;
}

3.列:指显示数据时的表格列,注意列id要与数据查询处理函数返回数据对象的key一致,否则无法显示;
4.数据操作:指对数据item的操作,比如删除、编辑、执行等。每个数据操作可以指定脚本和函数用于具体处理,处理函数原型为 function fn(itemIds, params){},itemIds为数组,当前操作数据的id;params为数组,透传参数。介绍最重要的两个配置,按钮和内容 按钮指在对话框底部显示的按钮,常用取消和确定两个。内容指在对话框中显示的内容,可以显示输入控件和文本。1)按钮,[{"name":"取消"}, {"name":"确定", "functionName":"confirmDelete"}],functionName表示最后处理操作的函数。2)内容,可以直接显示字符串,比如:是否确定删除?;也可以显示参数输入控件,[{"name":"名称","type":"string", "id":"name","sync":true},{"name":"值","type":"int","sync":true, "id":"value"}],注意sync表示初始化时是否同步显示数据项中的内容,一般用于编辑数据。
function confirmDelete(itemIds, params) {
    console.log('confirm delete', itemIds, params);
    var b = false;
    for (var id of itemIds) {
        b = dbDelete('test', [`id=${id}`]);
    }
    return b;
}

function confirmEdit(itemIds, params) {
    console.log('confirm edit', itemIds, params)
    var sets = [];
    for (var item of params) {
        if (item.name == 'device') {
            item.name = 'uuid';
        }
        var s = item.name + '=';
        if (item.type == 'string') {
            s+='"';
            s += item.value;
            s += '"';
        } else {
            s += item.value;
        }
        sets.push(s);
    }
    var b = false;
    console.log('sets:', sets);
    for (var id of itemIds) {
        b = dbUpdate('test', sets, [`id=${id}`]);
    }
    console.log('b:', b);
    return b;
}

5.批操作:批操作适用于多选清空,配置和操作一样,只是itemIds里包含选择的多个数据项id
6.增加操作:用于需要添加数据的情况,配置和操作一样,只是itemIds里包含选择的多个数据项id。处理函数为后端脚本函数用于处理具体的添加操作。 运行后系统会依据参数生成输入控件,用户输入的数据会透传给后端的confirmAdd函数
function confirmAdd(itemIds, params) {
    console.log('add=====> itemIds', itemIds, 'params', params);
    var obj = {};
    for (var p of params) {
        obj[p.name] = p.value
    }
    var ret = dbInsert('test', obj);
    console.log('ret:' + ret);
}

实战demo

需求:
假设我们有100台手机做任务,需求如下:
  1. 按顺序来启动每台手机任务,移动端脚本名为:test。
  2. 两次启动之间需要加一个随机的间隔时间。
  3. 将自定义的参数传给每个启动的手机任务,自定义参数类型:string,参数名:url。

需求分析:
  1. 以上需求本质上是做100台手机的调度功能,仅依靠前端的js脚本是无法实现的,所以必须使用微服务。
  2. 由于需要输入参数url,所以我们可以使用「业务处理」服务来实现,添加一个url参数。
  3. 两手机任务启动间隔时间也可以做成参数,让用户自己选择间隔的范围。所以可以添加为minInterval和maxInterval两个参数分别表示最小和最大间隔时间(毫秒)。

实现:
根据上述分析,我们可以先建立「业务处理」服务,然后再编写对应的「微服务脚本」来实现调度功能。
  1. 新建业务处理服务。在网页端选择【微服务】/【业务处理】,点击右上角的"+服务"按钮,新建一个名为demo的业务处理服务。
  2. 添加参数。点击"参数"按钮,添加三个参数,分别为:【名称:url,类型:字符串】;【名称:minInterval,类型:整型】;【名称:maxInterval,类型:整型】。
  3. 新建微服务脚本。在网页端选择【微服务】/【微服务脚本】,点击右上角的"+脚本"按钮,新建一个名为demo的脚本。
  4. 关联脚本和业务处理服务。在网页端选择【微服务】/【业务处理】,点击"编辑"按钮,选择刚创建的"demo"为脚本。
  5. 实现微服务脚本。脚本的核心功能为:接收参数,根据参数和调度要求来启动每个手机任务。
    // url、minInterval,maxInterval为业务处理服务透传过来的参数
    function main(url, minInterval, maxInterval) {
        var devices = deviceList();
        for (var device of devices) {
            // 仅调度在线手机
            if (device.onlineState == 1) {
                // 在手机上执行移动端名为"test"的脚本,这里的url参数会直接透传给移动端脚本的main函数参数。
                scriptExe('test', device.uuid, [url], true);
    
                // 在minInterval和maxInterval之间随机延时一段时间
                sleep(Math.random() * (maxInterval - minInterval) + minInterval);
            }
        }
    }                        
  6. 执行服务。在网页端【微服务】/【业务处理】,选择刚创建的"demo"服务,然后点击"执行"按钮,填充相关参数,点击"执行"即可执行代码。
  7. 如果您还没有手机端脚本(移动端脚本),可以参考下面的代码:
    // url参数的值,是上面的微服务脚本通过调用scriptExe函数透传过来的。
    function main(url) {
        console.log('url:' + url);
    }