3

CP宝——小程序云开发尝试

 3 years ago
source link: https://www.xiejingyang.com/2019/10/15/fast-dev-1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
CP宝——小程序云开发尝试 | Xieisabug

本文同时也是“ 快速开发系列 ”的第一篇,“快速开发系列”我将使用各种不同的技术进行产品的快速开发,每个产品的开发周期保持在1-2天/1人。

先看项目的简介视频,如果对项目感兴趣,可以再往下看:

看过视频的可以跳过这段,直接看初始化。我想开发的是一个给情侣或者夫妻之间使用的小工具,可以给对方发任务,同时提供奖励,这个小工具就是帮忙记录自己的任务和获得的奖励,免得对方不认账。

那么需求就是下面几点:

1.能绑定cp,2.能发任务接受,3.发任务人能够确认完成,4.能查看所有奖励,5.能使用奖励。

确定了奖励就可以进入编码阶段了。

在开通云开发之后,就拥有了云函数、云存储、数据库这三个能力,这个项目只用到云函数和数据库功能。

第一步,创建项目、数据库初始化。

云开发的数据库是一个JSON数据库,与mongodb类似,但是区别还是有点大的。

根据需求我们需要建立以下几个集合:

cpInfo:保存cp信息
taskList:任务集合
rewardList:奖励集合
history:历史记录
mostReward:最近使用的奖励名称集合

创建集合

创建完成集合之后,我们要设置一下集合的权限。因为我们的项目比较特殊,记录只能绑定了CP的两个人使用,无法使用云开发中自带的权限控制,所以所有的集合权限全部都设置为所有用户不可读写,全部使用云函数进行权限控制读写。

第二步,删除部分初始代码。因为创建云开发小程序的时候,微信会自动创建很多例子,所以我们要删除掉这些代码,只留下云函数中的login,后面会用到。

首先要完成绑定CP的功能,那么就先要知道当前用户有没有CP。

创建首页(home页面)并调用login云函数,获取到用户的openid,再用openid去查询有没有CP。

getOpenid: function () {
return new Promise((resolve) => { // 写成promise的方式比较好使用
// 调用云函数
wx.cloud.callFunction({
name: 'login',
data: {},
success: res => {
console.log('[云函数] [login] user openid: ', res.result)
app.globalData.openid = res.result.openid;
this.setData({
openid: res.result.openid
resolve();
fail: err => {
console.error('[云函数] [login] 调用失败', err)
getOpenid: function () {
    return new Promise((resolve) => { // 写成promise的方式比较好使用
        // 调用云函数
        wx.cloud.callFunction({
            name: 'login',
            data: {},
            success: res => {
                console.log('[云函数] [login] user openid: ', res.result)
                app.globalData.openid = res.result.openid;
                this.setData({
                    openid: res.result.openid
                })
                resolve();
            },
            fail: err => {
                console.error('[云函数] [login] 调用失败', err)
            }
        })
    })
},

获取到openid之后,就要去查询CP信息了,cpInfo的结构如下:

user1: "cp中一人的openid",
user2: "cp中另外艺人的openid",
createDate: "创建时间"
{
    user1: "cp中一人的openid",
    user2: "cp中另外艺人的openid",
    createDate: "创建时间"
}

创建一个叫做getCpInfo的云函数,使用云开发的api查询cpInfo集合:

// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
// API 调用都保持和云函数当前所在环境一致
env: cloud.DYNAMIC_CURRENT_ENV
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const db = cloud.database();
const _ = db.command;
//查询cpInfo
const cpInfo = await db.collection("cpInfo").where(_.or([
{ "user1": wxContext.OPENID },
{ "user2": wxContext.OPENID }
])).get();
if (cpInfo.data.length > 0) { // 查到了返回数据
return {
cpInfo: cpInfo.data[0]
} else { // 没查到返回空
return {
cpInfo: null
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({
    // API 调用都保持和云函数当前所在环境一致
    env: cloud.DYNAMIC_CURRENT_ENV
})


// 云函数入口函数
exports.main = async (event, context) => {
    const wxContext = cloud.getWXContext()
    const db = cloud.database();
    const _ = db.command;

    //查询cpInfo
    const cpInfo = await db.collection("cpInfo").where(_.or([
        { "user1": wxContext.OPENID },
        { "user2": wxContext.OPENID }
    ])).get();

    if (cpInfo.data.length > 0) { // 查到了返回数据
        return {
            cpInfo: cpInfo.data[0]
        }
    } else { // 没查到返回空
        return {
            cpInfo: null
        }
    }
}

在刚刚的home界面中,继续添加获取cpInfo的方法:

getCpInfo() {
return new Promise((resolve) => {
// 调用云函数
wx.cloud.callFunction({
name: 'getCpInfo',
data: {},
success: res => {
console.log('[云函数] [getCpInfo] ', res.result);
this.setData({
cpInfo: res.result.cpInfo,
loadCpInfo: true
resolve();
fail: err => {
console.error('[云函数] [getCpInfo] 调用失败', err)
getCpInfo() {
    return new Promise((resolve) => {
        // 调用云函数
        wx.cloud.callFunction({
            name: 'getCpInfo',
            data: {},
            success: res => {
                console.log('[云函数] [getCpInfo] ', res.result);
                this.setData({
                    cpInfo: res.result.cpInfo,
                    loadCpInfo: true
                });
                resolve();
            },
            fail: err => {
                console.error('[云函数] [getCpInfo] 调用失败', err)
            }
        })
    })
},

在home页面的 onLoad 方法中,调用者两个方法:

onLoad: function (options) {
this.getOpenid()
.then(this.getCpInfo);
onLoad: function (options) {
    this.getOpenid()
        .then(this.getCpInfo);
},

接下来就是完成绑定CP,思路为分享小程序出去,并携带自己的openid,打开分享链接自动绑定两个openid。为了防止分享出去的链接出现问题,可以再带上一个时间戳,打开的时候判断时间是否为一分钟之内,这样可以减少很多问题的发生。这里也是为了快速开发,否则有更多更加安全的方式进行绑定。

那么创建bind-cp界面,在页面上添加一个分享按钮:

<button open-type="share">分享给对方</button>
<button open-type="share">分享给对方</button>

处理当前页面的分享内容:

onShareAppMessage: function() {
return {
title: '来和我组成cp吧',
path: '/pages/bind-cp/bind-cp?id=' + getApp().globalData.openid + '&date=' + new Date().getTime()
onShareAppMessage: function() {
    return {
        title: '来和我组成cp吧',
        path: '/pages/bind-cp/bind-cp?id=' + getApp().globalData.openid + '&date=' + new Date().getTime()
    }
}

分享的就是当前页面,只不过带上了本人的openid和当前时间戳。当另一人打开了页面的时候,就调用另一个云函数,绑定cp。

创建云函数bindCp:

// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
// API 调用都保持和云函数当前所在环境一致
env: cloud.DYNAMIC_CURRENT_ENV
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext();
const {
OPENID
} = wxContext;
const db = cloud.database();
const _ = db.command;
const cpInfo = await db.collection("cpInfo").where(_.or([
{ "user1": wxContext.OPENID },
{ "user2": wxContext.OPENID }
])).get();
if (cpInfo.data.length > 0) {
return {
success: false,
message: "您已经绑定了CP。"
} else {
const addInfo = await db.collection("cpInfo").add({
data: {
user1: event.user1,
user2: OPENID,
createDate: new Date()
return {
success: true
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({
    // API 调用都保持和云函数当前所在环境一致
    env: cloud.DYNAMIC_CURRENT_ENV
})

// 云函数入口函数
exports.main = async (event, context) => {
    const wxContext = cloud.getWXContext();
    const {
        OPENID
    } = wxContext;
    const db = cloud.database();
    const _ = db.command;

    const cpInfo = await db.collection("cpInfo").where(_.or([
        { "user1": wxContext.OPENID },
        { "user2": wxContext.OPENID }
    ])).get();

    if (cpInfo.data.length > 0) {
        return {
            success: false,
            message: "您已经绑定了CP。"
        }
    } else {
        const addInfo = await db.collection("cpInfo").add({
            data: {
                user1: event.user1,
                user2: OPENID,
                createDate: new Date()
            }
        });

        return {
            success: true
        }
    }
    
}

在bind-cp页面的onLoad函数里,判断一下是不是来自分享,如果是分享过来的,那么就调用这个云函数绑定CP:

onLoad: function (options) {
const {id, date} = options;
if (id && date) {
if ((new Date().getTime() - parseInt(date) < 60000)) {
wx.cloud.callFunction({
name: "bindCp",
data: { user1: id },
success: res => {
console.log('[云函数] [bindCp]: ', res.result);
if (res.result.success) {
wx.showModal({
title: '恭喜',
content: "绑定成功",
showCancel: false,
success: () => {
wx.reLaunch({
url: '/pages/home/home',
} else {
wx.showModal({
title: '提示',
content: res.result.message,
showCancel: false
fail: err => {
console.error(err)
} else {
wx.showModal({
title: '超过时间了',
content: '只能在一分钟之内点击,请重新分享',
onLoad: function (options) {
    const {id, date} = options;

    if (id && date) {
        if ((new Date().getTime() - parseInt(date) < 60000)) {
            wx.cloud.callFunction({
                name: "bindCp",
                data: { user1: id },
                success: res => {
                    console.log('[云函数] [bindCp]: ', res.result);
                    if (res.result.success) { 
                        wx.showModal({
                            title: '恭喜',
                            content: "绑定成功",
                            showCancel: false,
                            success: () => {
                                wx.reLaunch({
                                    url: '/pages/home/home',
                                });
                            }
                        })
                    } else {
                        wx.showModal({
                            title: '提示',
                            content: res.result.message,
                            showCancel: false
                        })
                    }
                },
                fail: err => {
                    console.error(err)
                }
            })
        } else {
            wx.showModal({
                title: '超过时间了',
                content: '只能在一分钟之内点击,请重新分享',
            });
        }
        
    }
},

这样就完成了第一个流程——获取CP信息与绑定CP。

接下来就要发任务了,任务只包含一个名字和一个奖励列表,结构如下:

name: "",
rewardList: [],
fromOpenId: "",
toOpenId: "",
status: "", // 0 待确认, 1进行中, 2已完成, 3已放弃, 4被取消
createDate: ""
{
    name: "",
    rewardList: [],
    fromOpenId: "",
    toOpenId: "",
    status: "", // 0 待确认, 1进行中, 2已完成, 3已放弃, 4被取消
    createDate: ""
}

在home界面里,添加一个始终保持在右下角的按钮,点击按钮跳转到一个新的界面add-task,创建好add-task之后,为界面添加两个输入框,分别代表name和reward,一个增加奖励的按钮,一个提交任务的按钮。

%E5%8F%91%E9%80%81%E4%BB%BB%E5%8A%A1.png

创建addTask云函数,在新增任务的同时,保持最近3个奖励存储在mostReward:

// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
// API 调用都保持和云函数当前所在环境一致
env: cloud.DYNAMIC_CURRENT_ENV
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const db = cloud.database();
const _ = db.command;
const cpInfo = await db.collection("cpInfo").where(_.or([
{ "user1": wxContext.OPENID },
{ "user2": wxContext.OPENID }
])).get();
if (cpInfo.data.length > 0) {
let cp = cpInfo.data[0];
let toOpenId = cp["user1"] == wxContext.OPENID ? cp["user2"] : cp["user1"];
let addInfo = await db.collection("taskList").add({
data: {
fromOpenId: wxContext.OPENID,
toOpenId,
name: event.name,
rewardList: event.rewardList,
status: 0, // 0 待确认, 1进行中, 2已完成, 3已放弃, 4被取消
createDate: new Date()
const mostReward = await db.collection("mostReward").where({
openId: wxContext.OPENID
}).get();
if (mostReward && mostReward.data.length) {
let rewardList = mostReward.data[0].rewardList;
if (event.rewardList.length > 3) {
rewardList = event.rewardList.slice(0, 3);
} else {
rewardList = event.rewardList.concat(rewardList.slice(0, 3 - event.rewardList.length));
await db.collection("mostReward").doc(mostReward.data[0]._id).update({
data: {
rewardList
} else {
await db.collection("mostReward").add({
data: {
openId: wxContext.OPENID,
rewardList: event.rewardList.slice(0, 3)
return {
success: true
} else {
return {
success: false,
message: "您的cp信息有误,请联系管理员"
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({
    // API 调用都保持和云函数当前所在环境一致
    env: cloud.DYNAMIC_CURRENT_ENV
})

// 云函数入口函数
exports.main = async (event, context) => {
    const wxContext = cloud.getWXContext()

    const db = cloud.database();
    const _ = db.command;

    const cpInfo = await db.collection("cpInfo").where(_.or([
        { "user1": wxContext.OPENID },
        { "user2": wxContext.OPENID }
    ])).get();

    if (cpInfo.data.length > 0) {
        let cp = cpInfo.data[0];
        let toOpenId = cp["user1"] == wxContext.OPENID ? cp["user2"] : cp["user1"];

        let addInfo = await db.collection("taskList").add({
            data: {
                fromOpenId: wxContext.OPENID,
                toOpenId,
                name: event.name,
                rewardList: event.rewardList,
                status: 0, // 0 待确认, 1进行中, 2已完成, 3已放弃, 4被取消
                createDate: new Date()
            }
        });

        const mostReward = await db.collection("mostReward").where({
            openId: wxContext.OPENID
        }).get();
        if (mostReward && mostReward.data.length) {
            let rewardList = mostReward.data[0].rewardList;
            if (event.rewardList.length > 3) {
                rewardList = event.rewardList.slice(0, 3);
            } else {
                rewardList = event.rewardList.concat(rewardList.slice(0, 3 - event.rewardList.length));
            }

            await db.collection("mostReward").doc(mostReward.data[0]._id).update({
                data: {
                    rewardList
                }
            });
        } else {
            await db.collection("mostReward").add({
                data: {
                    openId: wxContext.OPENID,
                    rewardList: event.rewardList.slice(0, 3)
                }
            })
        }

        return {
            success: true
        }
    } else {
        return {
            success: false,
            message: "您的cp信息有误,请联系管理员"
        }
    }

}

添加了任务和最近奖励,接下来就是获取,同样创建云函数getTaskList,只需要查询待确认和正在进行的任务:

// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
// API 调用都保持和云函数当前所在环境一致
env: cloud.DYNAMIC_CURRENT_ENV
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const db = cloud.database();
const _ = db.command;
const taskList = await db.collection("taskList").where(_.or([
{ toOpenId: wxContext.OPENID, status: 0 },
{ toOpenId: wxContext.OPENID, status: 1 },
{ fromOpenId: wxContext.OPENID, status: 0 },
{ fromOpenId: wxContext.OPENID, status: 1 },
])).get();
return {
taskList: taskList.data
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({
    // API 调用都保持和云函数当前所在环境一致
    env: cloud.DYNAMIC_CURRENT_ENV
})

// 云函数入口函数
exports.main = async (event, context) => {
    const wxContext = cloud.getWXContext()

    const db = cloud.database();
    const _ = db.command;

    const taskList = await db.collection("taskList").where(_.or([
        { toOpenId: wxContext.OPENID, status: 0 },
        { toOpenId: wxContext.OPENID, status: 1 },
        { fromOpenId: wxContext.OPENID, status: 0 },
        { fromOpenId: wxContext.OPENID, status: 1 },
    ])).get();

    return {
        taskList: taskList.data
    }
}

其他几个查询列表的云函数也大同小异,这里就不多说了。

当点击任务的时候,需要根据任务的归属和任务当前的状态来决定业务逻辑:

1.接受任务。2.标记任务完成。3.放弃任务。4.删除任务。

创建云函数updateTask,传入id和修改的status:

// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
// API 调用都保持和云函数当前所在环境一致
env: cloud.DYNAMIC_CURRENT_ENV
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const db = cloud.database();
const _ = db.command;
let updateInfo = await db.collection("taskList").doc(event.id).update({
data: {
status: event.status
console.log(updateInfo);
return {
success: true
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({
    // API 调用都保持和云函数当前所在环境一致
    env: cloud.DYNAMIC_CURRENT_ENV
})

// 云函数入口函数
exports.main = async (event, context) => {
    const wxContext = cloud.getWXContext()

    const db = cloud.database();
    const _ = db.command;

    let updateInfo = await db.collection("taskList").doc(event.id).update({
        data: {
            status: event.status
        }
    });
    console.log(updateInfo);

    return {
        success: true
    }
}

不过“ 完成任务 ”的业务比较复杂,需要将奖励保存到rewardList,将完成历史保存到history,所以单独写一个云函数finishTask:

// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
// API 调用都保持和云函数当前所在环境一致
env: cloud.DYNAMIC_CURRENT_ENV
// 云函数入口函数
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const db = cloud.database();
const _ = db.command;
let taskDoc = db.collection("taskList").doc(event.id);
let updateInfo = await taskDoc.update({
data: {
status: 2
const cpInfo = await db.collection("cpInfo").where(_.or([
{ "user1": wxContext.OPENID },
{ "user2": wxContext.OPENID }
])).get();
const cpObj = cpInfo.data[0];
let toUser = cpObj["user1"] === wxContext.OPENID ? cpObj["user2"] : cpObj["user1"];
let taskObj = await taskDoc.get();
let rewardListCollection = db.collection("rewardList");
for (let i = 0; i < taskObj.data.rewardList.length; i++) {
await rewardListCollection.add({
data: {
openId: toUser,
name: taskObj.data.rewardList[i],
status: 0, // 0未使用, 1已使用
createDate: new Date()
await db.collection("history").add({
data: {
openId: toUser,
type: "finish-task",
name: taskObj.data.name,
rewardList: taskObj.data.rewardList,
createDate: new Date()
return {
success: true
} catch(e) {
console.error(e);
return {
success: false,
message: "更新失败"
// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({
    // API 调用都保持和云函数当前所在环境一致
    env: cloud.DYNAMIC_CURRENT_ENV
})

// 云函数入口函数
exports.main = async (event, context) => {
    const wxContext = cloud.getWXContext()

    const db = cloud.database();
    const _ = db.command;
    
    try {
        let taskDoc = db.collection("taskList").doc(event.id);
        
        let updateInfo = await taskDoc.update({
            data: {
                status: 2
            }
        });

        const cpInfo = await db.collection("cpInfo").where(_.or([
            { "user1": wxContext.OPENID },
            { "user2": wxContext.OPENID }
        ])).get();
        const cpObj = cpInfo.data[0];
        let toUser = cpObj["user1"] === wxContext.OPENID ? cpObj["user2"] : cpObj["user1"];

        let taskObj = await taskDoc.get();

        let rewardListCollection = db.collection("rewardList");
        for (let i = 0; i < taskObj.data.rewardList.length; i++) {
            await rewardListCollection.add({
                data: {
                    openId: toUser,
                    name: taskObj.data.rewardList[i],
                    status: 0, // 0未使用, 1已使用
                    createDate: new Date()
                }
            })
        }
        await db.collection("history").add({
            data: {
                openId: toUser,
                type: "finish-task",
                name: taskObj.data.name,
                rewardList: taskObj.data.rewardList,
                createDate: new Date()
            }
        })

        return {
            success: true
        }
    } catch(e) {
        console.error(e);
        return {
            success: false,
            message: "更新失败"
        }
    }
}

写到这里,整个小程序的大概就已经完成了,剩下的查询都大同小异。

云开发的数据库不是非常灵活,而且云函数的响应速度不是特别的快速,但是因为免费提供,在制作demo的阶段倒是可以用一用,而且是官方提供,所以在开发的时候也很方便,甚至可以直接在小程序的代码里操作后台数据库中的数据。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK