因为最近想实践一下小程序的云开发能力,于是设计开发了一个简单的投票应用,欢迎感兴趣的一起学习交流。
代码仓库 https://github.com/luosijie/m...
由于小程序【个人开发者】不开放【投票】类目,所以就不能在线预览了,我放几张应用的截图
数据库设计
总共用到了3个集合
1. users (用户集合)
基本上直接保存用用户的userInfo数据
{
"_id":"023ce9555ff068de0314b5521c313ee6",
"OPENID":"oZK45EoiyFzv...7R64I",
"nickName":"LSJ",
"gender":1,
"language":"zh_CN",
"city":"Xiamen",
"province":
"Fujian",
"country":"China",
"avatarUrl":"https://thirdwx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTL9lhZHZdYsMx3mjhZZYbbE5OZhUqUefNtsibkhdrSTIdpdhzv34lYHXtafMjuoibJ8JwTj5VM76CkA/132"
}
2. votes (投票集合)
{
"_id":"21ded5cb5ff5f0530407988a4e8f18a5", // 唯一id
"creator":"o-ZK45EoiyFzvevQyQTSZUV7R64I", // 发起人
"title":"阿斯顿大的as da", // 标题
"desc":"阿斯顿阿斯顿", // 描述
"startTime":"2021-1-7", // 开始日期
"endTime":"2021-1-8", // 结束日期
"state":"ing" // 状态
}
3. options (选项集合)
{
"_id":"be7fb3985ff5f05403068303431d580b", // 唯一id
"vote_id":"21ded5cb5ff5f0530407988a4e8f18a5", // 选项对应的投票_id
"title":"阿斯顿大的大的", // 标题
"desc":"撒打算的洒大地上阿斯顿", // 描述
"image":"http://tmp/2jVXjjLScAyNf0dffe2c5fc6479bee73fe954b64a3e7.png", // 配图
"users":["o-ZK45EoiyFzvevQyQTSZUV7R64I"] // 该选项的投票者
}
云函数开发
总共写了6个云函数
1. addRecord 新增投票记录
/**
* 新增投票记录
* @param {String} title 标题
* @param {String} desc 描述
* @param {String} startTime 开始日期
* @param {String} endTime 结束日期
* @param {String} anonymous 匿名
* @param {String} min 允许小投票数
* @param {String} max 允许最大投票数
* @param {String} type 投票类型:normal; pk
* @returns {Object} 包含投票_id
*/
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const voteCollection = db.collection('votes')
const data = {
creator: wxContext.OPENID, // 发起人
title: event.title,
desc: event.desc,
startTime: event.startTime,
endTime: event.endTime,
anonymous: event.anonymous,
min: event.min,
max: event.max,
type: event.type,
state: 'ing'
}
// 集合投票votes:新增记录
const res = await voteCollection.add({
data
})
// 集合选项options: 新增记录
const options = event.options
const optionCollection = db.collection('options')
const optionPromise = options.map( ele => {
const option = {
vote_id: res._id,
...ele
}
return optionCollection.add({
data: option
})
})
let resOptions = await Promise.all(optionPromise)
resOptions = resOptions.map(e => e._id)
// 返回投票结果
return {
success: true,
message: '新增投票成功',
...res
}
}
2.getRecordDetail 获取投票详情
/**
* 获取投票详情
* @param {String} _id 投票_id
* @return {Object} 投票数据
*/
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
exports.main = async (event, context) => {
const _id = event._id
const OPENID = cloud.getWXContext().OPENID
// 查找集合中的投票数据
const voteCollection = db.collection('votes')
// 聚合联表查询
const voteQuery = await voteCollection
.aggregate()
.match({ _id })
.lookup({
from: 'users',
localField: 'creator',
foreignField: 'OPENID',
as: 'creator'
})
.end()
let vote = {}
if (voteQuery && voteQuery.list.length) {
vote = voteQuery.list[0]
vote.creator = vote.creator[0]
// 判断是否当前投票的发起人
vote.isOwner = vote.creator.OPENID === OPENID
// 查找集合中的选项数据
const optionsCollection = db.collection('options')
const optionsQuary = await optionsCollection
.aggregate()
.match({ vote_id: _id })
.lookup({
from: 'users',
localField: 'users',
foreignField: 'OPENID',
as: 'users'
})
.end()
vote.options = optionsQuary.list
// 统计已经投票的人数
let votedTotal = 0
vote.options.forEach(e => {
if (e.users && e.users.length) {
votedTotal += e.users.length
}
})
vote.votedTotal = votedTotal
// 计算当前投票的状态
if (vote.state !== 'end') {
// 未开始
if (new Date().getTime() < new Date(vote.startTime).getTime()) {
vote.state = 'pre'
}
// 已过期 = 已结束
if (new Date().getTime() > new Date(vote.endTime).getTime()) {
vote.state = 'end'
}
}
return {
success: true,
data: vote
}
} else {
return {
success: false,
message: '找不到投票信息'
}
}
}
3. vote 投票操作
/**
* 投票操作
* @param {String} voteId 投票_id
* @param {String} optionId 选项_id
* @return {Object} 投票结果
*/
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
exports.main = async (event, context) => {
const _id = event.optionId
const vote_id = event.voteId
const OPENID = cloud.getWXContext().OPENID
// 获取当前投票数据对应的所有选项数据
const options = db.collection('options')
let voteOptions = await options.where({ vote_id }).get()
voteOptions = voteOptions.data
// 判断用户是否投过票
let curOptionUsers = []
for (let i = 0; i < voteOptions.length; i++) {
// 找到选项中所有投过票的用户
const users = voteOptions[i].users
if (users && users.length) {
if (voteOptions[i]._id === _id) {
curOptionUsers = users
}
if (users && users.length) {
// OPENID重复-说明已经投过票->直接返回
if (users.indexOf(OPENID) > -1) {
return {
success: false,
message: '您已经投过票了'
}
}
}
}
}
// 没有投票->将当前用户OPENID插入到对应的字段
curOptionUsers.push(OPENID)
const res = await options.where({ _id }).update({
data: {
users: curOptionUsers
}
})
return {
success: true,
data: res,
message: '投票成功'
}
}
4. getRecordPage 获取我的投票记录分页
/**
* 获取我的投票记录分页
* @param {Number} no 页码
* @param {Number} size 页数
* @return {Object} 投票数据列表和总数
*/
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
const size = event.size
//获取接口参数
const no = event.no
const OPENID = wxContext.OPENID
const voteCollection = db.collection('votes')
// 查找集合中的投票数据
const votes = await voteCollection.aggregate()
.match({
creator: OPENID
})
.lookup({
from: 'options',
localField: '_id',
foreignField: 'vote_id',
as: 'options'
})
.sort({
_id: -1
})
.skip((no - 1) * size)
.limit(size)
.end()
// 计算总数
const total = await voteCollection.count()
let data = votes.list
// 计算投票状态
if (data.length) {
data = data.map(e => {
if (e.state !== 'end') {
// 未开始
if (new Date().getTime() < new Date(e.startTime).getTime()) {
e.state = 'pre'
}
// 已过期 = 已结束
if (new Date().getTime() > new Date(e.endTime).getTime()) {
e.state = 'end'
}
}
// 统计已投票人数
let votedTotal = 0
const options = e.options
options.forEach(o => {
if (o.users && o.users.length) {
votedTotal += o.users.length
}
})
delete e.options
return {
...e,
votedTotal
}
})
}
return {
total,
data
}
}
5. login 登录注册
/**
* 登录注册
* @param {String} OPENID 从cloud.getWXContext()中获取
* @return {Object} 用书数据
*/
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
const db = cloud.database()
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
// 查找集合中的用户数据
const userCollection = db.collection('users')
const users = await userCollection.where({ OPENID: wxContext.OPENID }).get()
let user
if (users && users.data.length) {
// 用户已经存在-直接赋值用户数据
user = users.data[0]
} else {
// 新用户-向数据库插入用户数据
user = {
OPENID: wxContext.OPENID,
...event.userInfo
}
await userCollection.add({
data: user
})
}
// 返回用户数据-前端用来缓存
return {
...user
}
}
6. checkImage 校验图片合法性
/**
* 校验图片合法性
* @param {*} event.fileID 微信云存储的图片ID
* @return {Number} 0:校验失败;1:校验通过
*/
const cloud = require('wx-server-sdk')
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV
})
exports.main = async (event, context) => {
const contentType = 'image/png'
const fileID = event.fileID
try {
// 根据fileID下载图片
const file = await cloud.downloadFile({
fileID
})
const value = file.fileContent
// 调用 imgSecCheck 借口,校验不通过接口会抛错
// 必要参数 media { contentType, value }
const result = await cloud.openapi.security.imgSecCheck({
media: {
contentType,
value
}
})
return 1
} catch (err) {
return 0
}
}
前端开发
这次小程序端的开发
采用的是 滴滴前端团队出品的 mpx 框架
因为UI比较简单
这里就不贴代码了
感兴趣的欢迎前往 https://github.com/luosijie/m... 了解