更新记录

1.4.12(2021-08-31) 下载此版本

优化分页数据执行效率

1.4.11(2021-08-31) 下载此版本

优化分页执行效率

1.4.10(2021-08-31) 下载此版本

优化分页查询函数

查看更多

平台兼容性

阿里云 腾讯云 支付宝云
×

云函数类插件通用教程

使用云函数类插件的前提是:使用HBuilderX 2.9+


BaseCloud-V3 是什么?

base-cloud-v3基于uni-cloud-router拓展了枚举数据懒加载和类mysql语法的数据库操作能力,内置了常用的拦截器(中间件)模块。 它运行在服务端,可以让熟悉mysql的开发者基于类mysql语法,更为友好的编写MongoDb数据库操作代码。这里有两个示例来看看它做了什么:

const { Controller } = require("base-cloud-v3");

module.exports = class BookController extends Controller {

    constructor(ctx) {
        super(ctx, "baker_book");//当前controller绑定数据表baker_book
    }

    //对数据表baker_book基础的保存、修改功能,保存时创建数字自增的_id(兼容高并发场景)
    async save() {
        let start = 1000; //起始ID
        return await this.table.saveAutoId(this.ctx.data, start );
    }

    //对数据表baker_book基础的列表查询,并关联了baker_category分类表,查询书籍对应的分类名。
    async list(){
        return await this.table.list({
            select : "name as bookName,author",
            where : {status : 1},
            join : {
                select : "name as categoryId,type",
                from : "baker_category as bc",
                on : "bc._id = categoryId"
            },
            orderBy : "sort asc,createTime desc"
        })
    }

}

都使用类Mysql语法,与clientDb有什么不同?

base-cloud-v3 clientDB
运行在服务端 运行在客户端
不需要配置数据库权限 需配置数据库权限
传统服务端开发思维习惯 大前端开发思维习惯
学习成本相对较低 学习成本相对较高

二者并无绝对的优劣之分,建议开发者深入学习研究后选择使用。

云函数基础目录

本项目已内置uni-cloud-router,基于uni-cloud-router拓展了更丰富的能力,在阅读本项目文档之前,请确保您已对uni-cloud-router有所了解,如果还没有,那你可得抓紧了。

├── package.json
├── index.js // 云函数入口文件
├── config.js // 用于配置应用根目录、拦截器等
├── controller // 用于解析用户的输入,处理后返回相应的结果
|   ├── user.js
├── service (可选) //用于编写业务逻辑层,建议使用
|   ├── user.js
├── enums (可选) //用于编写枚举数据,建议使用
|   ├── UserGender.js

一、接管云函数

新建云函数api后,将以下代码直接复制到index.js文件中,让Router接管云函数,此处可引入一个配置文件,配置全局的拦截器。

'use strict';
const { Router } = require('base-cloud-v3') ;
//引入配置文件,配置拦截器
const config = require('./config.js');
const router = new Router(config);

exports.main = async (event, context) => {
    return router.serve(event, context)
};

二、修改配置文件

在云函数根目录下,创建config.js作为配置文件,将以下代码复制到config.js中:

const { LoginInter, PermissionInter, ResponseInter} = require("base-cloud-v3");
const uniID = require("uni-id");
module.exports = {
    baseDir: __dirname, // 项目根目录
    middleware: [
        [
            LoginInter({uniID}), // token校验拦截器
            {
                name: "LoginInter", 
                enable: true,
                ignore: []
            }
        ],
        [
            PermissionInter(), // 权限校验拦截器
            {
                name: "PermissionInter",
                enable: true,
                ignore: []
            }
        ],
        [
            ResponseInter(), // 响应结果统一处理拦截器
            {
                name: "ResponseInter",
                enable: true,
                ignore: []
            }
        ]
    ]
};

上述代码共配置了三个拦截器,用于登录验证、权限验证、默认响应结果等统一拦截处理,不需要的拦截器,可将其enable属性设置为false

三、引入公共模块

在上面两步中,我们使用了base-cloud-v3uni-id两个公共模块,故而为云函数绑定公共模块依赖,对云函数根目录右键选择管理公共模块依赖菜单,勾选base-cloud-v3uni-id后更新依赖。

四、创建Controller

1.创建基础Controller

在云函数根目录下创建controller文件夹,创建goods.js,编写一个Controller类,只需要继承自Controller即可,同时在构造函数中,为当前controller绑定一个数据表,示例如下:

const { Controller } = require("base-cloud-v3");

module.exports = class GoodsController extends Controller {

    constructor(ctx) {
        //将该controller绑定数据表"base_goods"
        super(ctx, "base_goods");
    }

    async list(){

    }

};

此处创建了一个专用于操作base_goods数据表的controller,可通过api/goods/list访问list()方法。其中api为当前云函数名称,goods为当前controller文件的文件名,list为list()函数名。

2.创建自动化Controller

步骤同上,将创建的controller继承对象由Controller改为BaseController后将开启13个操作当前数据表的增删改查接口。 需要注意的是,此时将直接暴露所有操作数据库的接口,实际业务中应在第二步中开启权限校验拦截器PermissionInter,将对应接口只开放给具有权限的用户。

const { BaseController } = require("base-cloud-v3");

module.exports = class GoodsController extends BaseController {

    constructor(ctx) {
        //1. 将该controller绑定数据表"base_goods"
        //2. 指定当前数据表中表示用户ID的字段名为userId(不指定时默认为uid)
        super(ctx, "base_goods" , "userId");
    }

};

15个操作数据库函数清单如下,其中最后6个与用户相关的接口,需要在第二步中开启登录校验拦截器使用:

函数名 必填参数 说明
info() _id 根据_id查询一条数据。
save() -- 保存、更新前端传入的数据到数据表,以_id字段为标识,数据库中不存在保存,存在则更新。
saveBySort() -- 同上,客户端未定义sort字段时自动生成排序。
list() -- 可选参数较多,详见下方说明。根据前端传入的参数查询列表数据,默认最多查询1000条。
listBySort() -- 同上,按sort字段排序。
page() -- 可选参数较多,详见下方说明。根据前端传入的参数查询分页列表数据。
remove() _id 根据_id删除一条数据。
batchRemove() where 根据前端传入的where条件批量删除数据。
clear() -- 清空数据表所有数据。
infoByUid() _id 根据_id查询一条属于当前用户的数据。
saveByUid() -- 基本逻辑同save()方法,保存时自动插入当前用户ID字段值,更新时会校验更新的数据是否属于当前用户。
listByUid() -- list()方法,但仅返回属于当前用户的数据列表。
pageByUid() -- page()方法,但仅返回属于当前用户的分页数据。
removeByUid() _id remove()方法,但是删除的数据不属于当前用户则无法删除。
batchRemoveByUid() where batchRemove()方法,但只会批量删除属于当前用户的数据。

例如:前端调用api/goods/info则直接会访问goods.js > info()

五、操作数据库

在已绑定数据表的controller的函数中,可直接使用以下方法来操作数据库。

1.1 保存、更新数据

传入即将入库的json数据作为参数,以_id字段为标识,若数据库中不存在则保存,存在则更新,同时会自动插入createTimeupdateTime两个字段,记录创建时间和更新时间。

let data = {a:1} ; //保存入库的数据,未定义时使用this.ctx.data
let saveData = { status : 1 } ; //保存时自动插入的数据
let updateData = { count : 1000 } ; //更新时自动插入的数据
let appendData = {saveData , updateData} ; //额外追加的数据
await this.table.save(data , appendData );

1.2 更新数据

含有_id的数据入库时,执行更新一条数据的操作。

let data = {_id:"xxxx" , name : "王小二"} ; //入库的数据必须含有_id字段,未定义data时将使用this.ctx.data
await this.table.update(data);

1.3 更新并返回当前这条数据

含有_id的数据入库时,执行更新一条数据的操作,然后返回数据库中更新后的数据,原子性操作。

let data = {_id:"xxxx" , name : "王小二"} ; //入库的数据必须含有_id字段
let returnData = true ; //更新后返回被更新的这条完整的数据
let dataInDb = await this.table.update(data , returnData);
console.log(dataInDb);

1.3 插入数据

直接向数据表内插入一条或多条数据,并在插入完成后向原始数据中注入对应的已入库数据的_id字段。

//1.插入一条数据
let data = {name : "王小二"} ; //入库的数据必须含有_id字段,未定义data时将使用this.ctx.data
let count = await this.table.add(data);
//count:1 插入数据的条数
//data:{ _id : "xxxxxxx" , name : "王小二"} //data中已注入入库的_id字段

//2.插入多条数据
let data = [ {name : "王小二"} , {name : "王大力"} ] ;
let count = await this.table.add(data);
//count:2 插入数据的条数
//data:[ { _id : "xxxxxxx1" ,name : "王小二"} , { _id : "xxxxxxx2" ,name : "王大力"} ]  //data中已注入入库的_id字段

2. 原子性自增id数据保存

saveAutoId()方法可在保存数据时生成原子性自增ID,支持高并发业务场景,需要注意的是,使用该方法,需要提前在云控制台创建好base_auto_number数据表(腾讯云,阿里云会自动创建)。 同时,将产生额外的空间费用,根据目前腾讯云的计费标准计算,每插入100w条数据,大约增加13.00元的费用成本。

let data = {a:1} ; //保存入库的数据,未定义时使用this.ctx.data
let start = 1000 ; //自增起始ID,未定义时默认1,仅插入第一条数据时有效。
await this.table.saveAutoId(data , start);

//追加额外数据时
let data = {a:1} ; //保存入库的数据,未定义时使用this.ctx.data
let start = 1000 ; //自增起始ID,未定义时默认1,仅插入第一条数据时有效。
let saveData = { status : 1 } ; //保存时自动插入的数据
let updateData = { count : 1000 } ; //更新时自动插入的数据
let appendData = {saveData , updateData , start } ; //额外追加的数据
await this.table.saveAutoId(data , appendData );

3. 查询数据列表

可关联一个或多个数据表进行查询,关联一个时join参数为json,关联多个时join参数为数组。

await this.table.list({
    //unwind : "" ,  //接受一个拆解的数组字段名
    select : "_id,name as goodsName,userId",
    where : {
        status : 1
    },
    join : {
        select : "name as userName,mobile" ,
        from : "uni-id-users as user" , //关联uni-id-users并重命名为user,type为Object、Array时以user作为字段名
        on : "user._id = userId", //此处的user表示from中的uni-id-users
        /**
         * type 关联数据结果的数据结构类型
         * 可选值为:Root表示将关联数据结果合并到根节点,
         * Object表示将关联结果取第一条数据作为json数据返回,
         * Array表示将关联结果以数组形式返回,默认root。
         */
        //type : "root" 
    },
    unselect: "userId",//从最终的字段中删减指定字段
    orderBy : "name asc,createTime desc" ,
    limit : 100
});

4. 查询分页数据

可关联一个或多个数据表进行查询,关联一个时join参数为json,关联多个时join参数为数组。

let page = await this.table.paginate({
    totalRow : true , //是否返回总页码,默认true。
    pageNumber:1 , //页码
    pageSize : 10 , //分页条数

    /**
     * 动态查询条件,即前端传入查询参数不为空时追加查询条件
     * 1. 此处追加mobile的equal条件,数据库字段名和前端传入查询参数名均为mobile,二者一致时使用字符串表示。
     * 2. 此处追加username的equal条件,数据库字段名为username,前端传入查询参数名为name,二者不一致时使用数组表示。
     */
    eq:["mobile", ["username","name"] ], //等于
    neq:[], //不等于
    like:[],//模糊匹配
    gt:[],//大于
    gte:[],//大于等于
    lt:[],//小于
    lte:[],//小于等于
    in:[],//在数组中
    nin:[],//不在数组中

    /**
     * 动态范围查询条件,即前端传入查询参数不为空时追加范围查询条件
     * 1. 此处追加createTime的>=且<=范围查询条件,数据库字段名为createTime,默认前端传入开始时间和结束时间参数名为:createTimeStart、createTimeEnd
     * 2. 此处追加的registTime的>=且<=范围查询条件,数据库字段名为registTime,另行制定前端传入的开始时间和结束时间的参数名为:startTime、endTime
     */
    range:["createTime", ["registTime","startTime","endTime"] ], //大于等于且小于等于
    neqRange:[], //大于且小于
    gteRange:[],//大于等于且小于
    lteRange:[],//大于且小于等于

    //unwind : "" ,  //接受一个拆解的数组字段名
    select : "_id,name as goodsName,userId",
    where : {
        status : 1
    },
    join : {
        select : "name as userName,mobile" ,
        from : "uni-id-users as user" , //关联uni-id-users并重命名为user,type为Object、Array时以user作为字段名
        on : "user._id = userId", //此处的user表示from中的uni-id-users
        //type : "root" //将关联数据结果合并到根节点,可选值为:Object表示将关联结果取第一条数据作为json数据返回,Array表示将关联结果以数组形式返回,默认root。
    },
    unselect: "userId",
    orderBy : "name asc,createTime desc" 
});

返回标准数据结构如下:

{
    pageNumber : 1 , //当前页码
    pageSize : 10 , //分页条数
    list : [] , //当前页数据列表
    totalRow : 100 , //总数据条数
    totalPage : 10 , //总页码
    lastPage : false , //是否最后一页
    firstPage : true  //是否第一页
}

5. 主键查询

接受_id作为参数,若不传入时,将从this.ctx.data中取_id参数,数据存在时返回json,不存在时返回null

let _id = "your doc _id" ;
await this.table.findById(_id);

另外可以传入一个json,定义额外参数。

let data = await this.table.findById({
    _id : "your doc _id" , //缺省时为this.ctx.data._id
    select : "name as goodsName,userId", //选取字段
    join : {
        select : "name as userName,mobile" ,
        from : "uni-id-users as user" , //关联uni-id-users并重命名为user,type为Object、Array时以user作为字段名
        on : "user._id = userId", //此处的user表示from中的uni-id-users
        //type : "root" //将关联数据结果合并到根节点,可选值为:Object表示将关联结果取第一条数据作为json数据返回,Array表示将关联结果以数组形式返回,默认root。
    },
    unselect:"userId" //从最终选取的字段中删减指定字段
});

6. 查询一条数据

findById()不同的是,可以定义一个where参数作为查询筛选条件,定义orderBy参数作为排序条件。数据存在时返回json,不存在时返回null

let data = await this.table.findFirst({
    select : "name as goodsName,userId",
    where : {} ,
    join : {
        select : "name as userName,mobile" ,
        from : "uni-id-users as user" , //关联uni-id-users并重命名为user,type为Object、Array时以user作为字段名
        on : "user._id = userId", //此处的user表示from中的uni-id-users
        //type : "root" //将关联数据结果合并到根节点,可选值为:Object表示将关联结果取第一条数据作为json数据返回,Array表示将关联结果以数组形式返回,默认root。
    },
    unselect:"userId",
    orderBy : "name asc,createTime desc"
});

7. 求和、计数查询

指定一个或多个字段进行求和,并统计总数据条数。

let {count , nums , amount} = await this.table.sum({
    where : { status : 1 } , //指定筛选范围,未指定时将全表查询
    select : "nums,amount" , //指定求和字段,多个使用英文逗号连接
    countKey : "count" //指定总数据条数字段键名,未指定时默认:count
})

//深层键名字段需使用 as 作为别名
let {count , totalPrice , amount } = await this.table.sum({
    select : "priceData.price as totalPrice,amount" //指定求和字段,多个使用英文逗号连接,深层键名字段需使用 as 作为别名
})

使用count()方法查询指定数据的数据条数,未定义查询范围时,查询全表数据的条数。

let totalCount = await this.table.count(); //全表数据条数

let count = await this.table.count({status : 1}); //status=1的数据的条数 

8. 删除一条数据

使用_id参数删除数据,若不传入时,则取用this.ctx.data._id作为参数。

let _id = "your doc _id" ;
let isSuccess = await this.table.remove(_id); //返回是否删除成功 : true | false

9. 批量删除数据

let where = {} ; //筛选条件
let isSuccess = await this.table.batchRemove(where); //返回是否删除成功 : true | false

10. 清空数据表

清空数据表内所有数据。

let isSuccess = await this.table.clear(); //返回是否删除成功 : true | false

11. 判断唯一值

在保存、新增数据时,对即将入库的数据中的指定字段,进行唯一值校验。验证通过时返回true,否则返回false

let isUnique = await this.table.isUnique({
    field : "name" , //唯一值校验的字段名,必填
    where:{}, //筛选条件,用于限定数据唯一性的范围,不传入时则全表唯一。
    data : { //即将保存、更新入库的数据,不传入时使用this.ctx.data作为参数
        _id : "your doc _id" , //更新时应包含更新数据的_id字段
        name : "your save name" //即将保存、更新入库的字段
    },
    required : false //设置为false,表示若data.name为空值,则跳过唯一性验证;设置为true时,若为空则抛出异常,默认true。
});

12. 插入自增排序字段

在保存、新增数据时,若即将入库的数据中没有排序字段值,则向其中插入自增的排序值,注意此处仅插入自然排序,非原子性自增,不适用于高并发场景。

let data = {} ; //即将入库的data数据
await this.table.setSort({
    where:{}, //可选参数,查询最大的排序值时的筛选条件
    data, //可选参数,未定义时将使用this.ctx.data作为参数
    sortKey:"number", //可选参数,排序字段名,未指定时为:sort。
    step : 1 , //可选参数,排序自增步长,默认10(方便自行调整顺序)。
    start : 1 //初始排序值,默认1
})

13. 获取动态查询条件

根据前端传入的参数,筛选出非空的值,创建查询条件。注意:这是一个同步函数,调用时不需要await修饰符。

let data = {mobile:15811111111,name:"王小二"} ; //前端传入的参数,未定义时默认使用this.ctx.data
let where = this.table.getWhere({
    where : {} , //固定静态查询条件,
    /**
     * 动态查询条件,即前端传入查询参数不为空时追加查询条件
     * 1. 此处追加mobile的equal条件,数据库字段名和前端传入查询参数名均为mobile,二者一致时使用字符串表示。
     * 2. 此处追加username的equal条件,数据库字段名为username,前端传入查询参数名为name,二者不一致时使用数组表示。
     */
    eq:["mobile", ["username","name"] ], //等于
    neq:[], //不等于
    like:[],//模糊匹配
    gt:[],//大于
    gte:[],//大于等于
    lt:[],//小于
    lte:[],//小于等于
    in:[],//在数组中
    nin:[],//不在数组中

    /**
     * 动态范围查询条件,即前端传入查询参数不为空时追加范围查询条件
     * 1. 此处追加createTime的>=且<=范围查询条件,数据库字段名为createTime,默认前端传入开始时间和结束时间参数名为:createTimeStart、createTimeEnd
     * 2. 此处追加的registTime的>=且<=范围查询条件,数据库字段名为registTime,另行制定前端传入的开始时间和结束时间的参数名为:startTime、endTime
     */
    range:["createTime", ["registTime","startTime","endTime"] ], //大于等于且小于等于
    neqRange:[], //大于且小于
    gteRange:[],//大于等于且小于
    lteRange:[],//大于且小于等于
}, data );

14. 聚合查询

以上操作数据的接口覆盖大多数业务场景,需要编写复杂的聚合查询命令时,可创建聚合查询对象后,根据业务场景,自行编写聚合查询阶段命令。

//创建本数据表的聚合查询对象
let agg = this.table.agg(); 

//筛选条件
agg.where({ name : "xxxx" }) ; 

//选取指定字段
agg.select("name as goodsName,price,userId");

//追加字段,详情请参考聚合查询语法:https://uniapp.dcloud.io/uniCloud/cf-database?id=addfields
agg.addFields({
    "myName" : "$name"
});

//移除指定字段
agg.unselect("price,userId");

//指定根节点的数组字段,取数组第一个元素,然后合并到根节点中。
agg.merge("userList");

//截取数组字段
agg.slice({
    "userList" : [0,2] , //从userList字段的第0个元素开始,截取2个元素。
    "orderList" : [5,10], //从orderList字段的第5个元素开始,截取10个元素。
});

//排序
agg.sort("name,createTime desc"); //根据name正序排序,name相同时根据createTime倒序排序。

//拆分数组字段,以下两种写法均可,其他传参用法参考:https://uniapp.dcloud.io/uniCloud/cf-database?id=unwind
agg.unwind("name");
agg.unwind("$name");

//限制返回数据条数
agg.limit(100);

//分页,内部整合了skip、limit
agg.page(pageNumber,pageSize); //pageNumber为页码,从1开始;pageSize为单页条数

//以下为返回聚合操作结果的函数:

//返回列表数据,为空时返回空数组 []
let list = await agg.get();

//返回一条数据,为空时返回null
let data = await agg.getFirst();

//返回分页列表数据,若为空时返回空数组 []
let pageNumber = 1 , pageSize = 10 ;
let pageList = await agg.getPage(pageNumber,pageSize);

//返回具体的数量,如:0、100
let count = await agg.getCount(); 

//以下为未封装命令,详情参考文档https://uniapp.dcloud.io/uniCloud/cf-database?id=%e8%81%9a%e5%90%88%e8%a1%a8%e8%be%be%e5%bc%8f

//https://uniapp.dcloud.io/uniCloud/cf-database?id=project
agg.project();

//https://uniapp.dcloud.io/uniCloud/cf-database?id=bucket
agg.bucket();

//https://uniapp.dcloud.io/uniCloud/cf-database?id=bucketauto
agg.bucketauto();

//https://uniapp.dcloud.io/uniCloud/cf-database?id=count
agg.count();

//https://uniapp.dcloud.io/uniCloud/cf-database?id=geonear-1
agg.geoNear();

//https://uniapp.dcloud.io/uniCloud/cf-database?id=group
agg.group();

//复杂链表:https://uniapp.dcloud.io/uniCloud/cf-database?id=lookup
agg.lookup();

//https://uniapp.dcloud.io/uniCloud/cf-database?id=replaceroot
agg.replaceroot();

//https://uniapp.dcloud.io/uniCloud/cf-database?id=sample
agg.sample();

//https://uniapp.dcloud.io/uniCloud/cf-database?id=skip
agg.skip();

//https://uniapp.dcloud.io/uniCloud/cf-database?id=sortbycount
agg.sortbycount();

六、公共方法

当多个controller需要调用同一个方法,或绑定A数据表的controller需要操作B数据表的数据时,可通过定义service来调用,同时多个不同的service之间也可以互相调用。

在云函数根目录创建service目录,创建goodsService.js文件,定义GoodsService继承自Service类,在构造函数中绑定数据表base_goods

const {Service} = require("base-cloud-v3");

module.exports = class GoodsService extends Service {

    constructor(ctx) {
        //1. 将该service绑定数据表"base_goods"
        //2. 指定当前数据表中表示用户ID的字段名为userId(不指定时默认为uid)
        super(ctx, "base_goods" , "userId");
    }

};

v1.2.0起,Service类中已内置info()findFirst()等基础函数,此时在任意controller、service中可以使用await this.service.goodsService.info()来调用goodsService.js中的info()方法,如:

const { Controller } = require("base-cloud-v3");

module.exports = class OrderController extends Controller {

    constructor(ctx) {
        //绑定base_order表
        super(ctx, "base_order");
    }

    async saveOrder(){
        //在订单相关的controller中调用商品相关的service方法
        let goodsId = "xxx" ;
        let goods = await this.service.goodsService.info(goodsId);
    }

};

基础函数清单如下:

函数名 必填参数 说明
info() _id 根据_id查询一条数据。
save() -- 保存、更新前端传入的数据到数据表,以_id字段为标识,数据库中不存在保存,存在则更新。
add() -- 添加一条数据到数据表
update() -- 更新一条数据到数据表,以_id字段为标识。
saveBySort() -- 同上,客户端未定义sort字段时自动生成排序。
list() -- 可选参数较多,详见下方说明。根据前端传入的参数查询列表数据,默认最多查询1000条。
listBySort() -- 同上,按sort字段排序。
remove() _id 根据_id删除一条数据。
batchRemove() where 根据前端传入的where条件批量删除数据。
clear() -- 清空数据表所有数据。

七、枚举数据

在云函数根目录中创建enums文件夹,创建UserGender.js,定义含有titlevaluekey字段的数组数据,其中key字段可根据使用场景选填。

  1. title: 展示数据时使用,可随意变动;
  2. value: 存入数据库时使用,通常为数字,不会变动;
  3. key : 编写代码时使用,通常为英文,不会变动。
const {Enums} = require("base-cloud-v3") ;

module.exports = class UserGenderEnum extends Enums {

    constructor(){
        let list = [
            {title : "未知" , value : "0" , "key" : "unknow"} ,
            {title : "男" , value : "1" , "key" : "male" } ,
            {title : "女" , value : "2" , "key" : "female"} 
        ];
        super(list);
    }

}

在任意controller、service中可以直接使用枚举数据:

const { Controller } = require("base-cloud-v3");

module.exports = class UserController extends Controller {

    constructor(ctx) {
        super(ctx, "uni-id-users");
    }

    //返回UserGender枚举数据列表
    async genders(){
        return {
            list : this.enums.UserGender.list
        };
    }

    async list(){
        let list = await this.table.list();
        list.forEach(item=>{
            //根据value获取对应的title,展示给前端
            item.genderName = this.enums.UserGender.title(item.gender) ;

            //以下示例只为说明内置方法,在此处无实际意义

            //直接使用key属性获取对应的value
            item.gender = this.enums.UserGender.female ; //3

            //根据value获取key
            item.genderKey = this.enums.UserGender.key(item.gender) ;

            //根据value或key获取数组下标
            item.genderIndex = this.enums.UserGender.index(item.gender) ;
            item.genderIndex = this.enums.UserGender.index(item.genderKey) ;

            //根据value或key获取整条枚举数据(包含title、value、key等字段)
            item.genderData = this.enums.UserGender.data(item.gender) ;
            item.genderData = this.enums.UserGender.data(item.genderKey) ;

        })
        return { list };
    }

};

八、深入学习

base-cloud内置了四个基础拦截器,开发者可通过阅读其逻辑代码,深入了解其运行原理,为未来业务开发提供便利。

LoginInter()

PermissionInter()

HttpInter()

TranactionInter()

ResponseInter()

1. LoginInter()

登录校验拦截器,可接收配置参数如下:

loginInter({
    invalidTokenCode : "invalidToken", //token无效时返回的状态码标识
    tokenKey : "uniIdToken", //客户端token字段键名
    uniID : uniID //uniID对象,需引入uni-id公共模块
})

2. PermissionInter()

权限校验拦截器,基于uni-id-usersuni-id-roles数据表的数据进行权限校验,权限数据缓存于用户的token中,用户的role中包含admin时为超级管理员,不校验权限:

PermissionInter()

3. ResponseInter()

3.1 响应结果统一处理拦截器,可接收配置参数如下,以下配置为默认配置,可修改为其他的自定义值:
ResponseInter({
    codeKey : "code", //状态码键名
    messageKey : "message", //提示信息键名
    success : { //业务响应成功时返回的默认响应结果
        code : "ok" ,
        message : ""
    },
    fail : { //业务响应失败时返回的默认响应结果
        code : "fail",
        message : "操作失败"
    }
})
3.2 基于默认响应结果,返回业务响应结果。
//1. 返回true、undefined、json、array数据,表示响应成功
return true ;

//2.返回一个字符串或false,表示响应失败
return "错误提示信息" ;
3.3 基于默认响应结果,在业务函数中可简化响应结果代码编写:

const { Controller } = require("base-cloud-v3");

module.exports = class UserController extends Controller {
    constructor(ctx) {
        super(ctx, "uni-id-users");
    }

    async info() {
        let data = await this.table.info();
        return { data };
    }

    async save() {
        let isUnique = await this.table.isUnique({field:"username"});
        if(!isUnique){
            return "用户名已存在" ;
        }
    }

};

前端访问api/user/info接口时,将得到响应结果如下:

{
    code : "ok" ,
    message : "" ,
    data : {
        //...
    }
}
3.4 当返回字符串时,默认响应结果会处理为业务处理失败的响应。
//api > controller > user.js

async save() {
    let isUnique = await this.table.isUnique({field:"username"});
    if(!isUnique){
        return "用户名已存在" ;
    }
    await this.table.save();
}

前端访问api/user/save接口时,若提交了一个重复的用户名,此时前端收到的响应结果为:

{
    code : "fail" ,
    message: "用户名已存在"
}

保存更新成功时,前端收到的响应结果为:

{
    code : "ok" ,
    message : ""
}

4. TransactionInter()

[注意] 使用该拦截器时,务必保证操作的数据表已创建完成,否则将抛出异常。

开启事务,当抛出异常时,进行回滚。在被拦截的controllerservice中使用this.ctx.tranaction可以获取到当前线程中的回滚数据库对象, 基于该对象获取collection实例,而后进行add()update()操作,需要注意的是事务本身不支持批量增加、修改操作,详见数据库事务

//在数据表一的controller中
async save(){
    let tCollectionA = this.ctx.transaction.collection(this.table.name); //获取当前集合的事务集合对象,建议设置为代码片段 :)
    await tCollectionA.add({name:"xxx"}); //执行数据表一的添加操作
    await this.service.demoService.update({_id:"docId",name:"cccc"});//执行数据表二的更新操作
}
//在数据表二的service中
async update(data){
    let tCollectionB = this.ctx.transaction.collection(this.table.name); //获取当前集合的事务集合对象
    let updated = await tCollectionB.doc(data._id).update({name:data.name});//执行数据表一的添加操作
    if(updated == 0){
        throw new Error("操作失败"); //抛出异常时将进行数据库操作回滚
    }
}

该拦截器在配置文件中应放在拦截器数组中最后一个位置才有效,如下为config.js配置文件中的使用示例:

const { LoginInter,  ResponseInter, TransactionInter } = require("base-cloud-v3");
const uniID = require("uni-id");

let code = "systemError" ; //抛出异常触发事务回滚时的反馈给前端状态码,默认:fail
let message = "系统错误" ; //抛出异常无异常信息时的默认反馈信息

module.exports = {
    baseDir: __dirname, // 项目根目录
    middleware: [
        [
            LoginInter({ uniID }), // token校验
            {
                name: "LoginInter",
                enable: true,
                ignore: []
            }
        ],
        [
            ResponseInter(), // 响应结果
            {
                name: "ResponseInter",
                enable: true
            }
        ],
        [
            TransactionInter(code,message), // 事务
            {
                name: "TransactionInter",
                enable: true,
                match:[ "user/sign/save"]
            }
        ]
    ]
};

5. HttpInter()

禁止通过外网链接访问,只能通过云函数调用。

九、客户端调用

1. 使用 uniCloud 访问

uniCloud.callFunction({
  name: 'api', // 要调用的云函数名称
  data: {
    action: 'user/login', // 路由地址,对应 controller 下 user.js 的 login 方法
    // 参数列表
    data: {
      // controller 通过 this.ctx.data 获取
      username: 'demo',
      password: 'demo',
    },
  },
})

2. 使用 URL 化 request 访问

uni.request({
  url: 'xxxxx/api/user/login', // 路由地址,对应 controller 下 user.js 的 login 方法
  data: {
    // controller 通过 this.ctx.data 获取
    username: 'demo',
    password: 'demo',
  },
})

十、其他

1. 抓取网络图片保存至云存储

let url = "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png" ; //网络图片链接
let fileName = "baidu.jpg" ; //转储后的文件名
let cdnUrl = await this.$b.fetchImage(url , fileName);

2. 通用加密算法

加密算法:将所有非空参数按字典序排序,最后加上秘钥,进行md5加密,然后转大写,形成签名字符串。

2.1 获取签名

使用sign()函数直接获取签名:

let data = { c : 3 , a : 1 , b : 2 , sign : "xxxxx" } ;
let escapeKey = ["sign"] ; //data中不参与签名的键名,默认["sign"]
let join = "&" ; //用于参数拼接的字符串
let secret = "your secret" ; //参与签名的秘钥
let secretKey = "key" ; //秘钥追加到参数后时定义的键名,未定义时不追加

//加密原始字符串: a=1&b=2&c=3&key=your secret
let sign = this.$b.sign ({data , secret , escapeKey , join , secretKey }); //将得到一个32位的大写字符串
2.2 签名验证

使用verifySign()进行签名验证:

let data = { c : 3 , a : 1 , b : 2 , sign : "xxxxx" } ;
let sign = data.sign ; //进行比对的签名
let escapeKey = ["sign"] ; //data中不参与签名的键名,默认["sign"]
let join = "&" ; //用于参数拼接的字符串
let secret = "your secret" ; //参与签名的秘钥
let secretKey = "key" ; //秘钥追加到参数后时定义的键名,未定义时不追加

//加密原始字符串: a=1&b=2&c=3&key=your secret
let isVaild = this.$b.verifySign ({data, escapeKey , secret , join , secretKey , sign}); //true | false 
2.3 获取加密原始字符串

使用getSignStr(data , escapeKey , join) 函数获取加密字符串,获取后用于使用自定义加密算法加密,例:

let data = { c : 3 , a : 1 , b : 2 , sign : "xxxxx" } ;
let escapeKey = ["sign"] ; //data中不参与签名的键名,默认["sign"]
let join = "&" ; //用于参数拼接的字符串
let signStr = this.$b.getSignStr (data , escapeKey , join ); // a=1&b=2&c=3

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问