const uniID = require('uni-id-common'); const db = uniCloud.database(); const _ = db.command; const VOTE_DB = 'cms-vote'; const VOTE_INFO_DB = 'cms-vote-info'; module.exports = { _before: function () { // 通用预处理器 this.uniID = uniID.createInstance({ context: this.getClientInfo() }); }, async getVoteInfo(query) { try { const { vote_id } = query || {}; if (!vote_id) return { code: 400, msg: '缺少 vote_id' }; // 只能查自己的投票记录,避免通过传 user_id 探测他人选择 const payload = await this.uniID.checkToken(this.getUniIdToken()); const user_id = payload.errCode ? null : payload.uid; const user_res = user_id ? await db.collection(VOTE_DB).where({ user_id, vote_id }).get() : { data: [] }; const all_res = await db.collection(VOTE_INFO_DB).where({ vote_id }).get(); // 返回可安全解构的默认值:页面模板会直接访问 // all_data.selectValue[...] / all_data.count,null 会导致渲染报错 return { code: 200, user_data: user_res.data[0] || {}, all_data: all_res.data[0] || { vote_id, selectValue: {}, count: 0 } }; } catch (e) { return { code: 500, msg: e.message }; } }, async add_vote(query) { const payload = await this.uniID.checkToken(this.getUniIdToken()); if (payload.errCode) throw new Error('登录状态失效,请重新登录'); const user_id = payload.uid; // 以服务端令牌为准,忽略客户端传入 const { vote_id, selectValue, type } = query || {}; if (!vote_id || !selectValue) { return { code: 400, msg: '参数不完整' }; } // 用值的形态判断单选/多选,而不是比较文案。前端的类型名里混用了 // 全角"("和半角")",按字符串比较容易漏判。 const options = Array.isArray(selectValue) ? selectValue.slice() : [selectValue]; if (!options.length) { return { code: 400, msg: '未选择任何选项' }; } const transaction = await db.startTransaction(); try { // 投票记录与统计必须同事务,否则会出现"记录已投但票数没加"的状态 const exist = await transaction.collection(VOTE_DB) .where({ user_id, vote_id }) .count(); if (exist.total > 0) { await transaction.rollback(); return { code: 409, msg: '已投票!' }; } await transaction.collection(VOTE_DB).add({ vote_id, selectValue, user_id, type, create_time: Date.now() }); // 原子自增。原实现是"读出整个 selectValue → 改 → 写回", // 并发投票时会互相覆盖,导致票数丢失。 const incData = { count: _.inc(1) }; for (const option of options) { incData[`selectValue.${option}`] = _.inc(1); } const infoRes = await transaction.collection(VOTE_INFO_DB) .where({ vote_id }) .update(incData); // 首次投票时统计行还不存在,补建 if (!infoRes.updated) { const initValue = {}; for (const option of options) { initValue[option] = 1; } await transaction.collection(VOTE_INFO_DB).add({ vote_id, selectValue: initValue, type, count: 1 }); } await transaction.commit(); return { code: 200 }; } catch (e) { await transaction.rollback(); return { code: 500, msg: e.message }; } } }