因为最近想实践一下小程序的云开发能力,于是设计开发了一个简单的投票应用,欢迎感兴趣的一起学习交流。
代码仓库 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...