// 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj // jsdoc语法提示教程:https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129 // 导入 createConfig 模块 const createConfig = require('uni-config-center'); // 导入 buildTemplateData 模块 const buildTemplateData = require('./build-template-data'); // 导入 utils 模块中的 parserDynamicField 函数 const { parserDynamicField } = require('./utils'); // 导入 schema-name-adapter 模块 const schemaNameAdapter = require('./schema-name-adapter'); // 导入 preset-condition 模块中的 presetCondition 和 conditionConvert 函数 const { presetCondition, conditionConvert } = require("./preset-condition"); // 导入 uni-sms-co 模块 const uniSmsCo = uniCloud.importObject('uni-sms-co'); // 导入 uniCloud.database 模块 const db = uniCloud.database(); // 使用 createConfig 函数创建 smsConfig 对象 const smsConfig = createConfig({ pluginId: 'uni-sms-co' }).config(); // 定义 errCode 函数,返回错误码 function errCode(code) { return 'uni-sms-co-' + code; } // 定义 tableNames 对象 const tableNames = { template: 'opendb-sms-template', // 模板表名为 'opendb-sms-template' task: 'opendb-sms-task', // 任务表名为 'opendb-sms-task' log: 'opendb-sms-log' // 日志表名为 'opendb-sms-log' }; module.exports = { _before: async function () { // 通用预处理器 if (!smsConfig.smsKey || smsConfig.smsKey.length <= 20 || !smsConfig.smsSecret || smsConfig.smsSecret.length <= 20) { throw new Error('请先配置smsKey和smsSecret') } this.tableNames = tableNames /** * 优化 schema 的命名规范,需要兼容 uni-admin@2.1.6 以下版本 * 如果是在uni-admin@2.1.6版本以上创建的项目可以将其注释 * */ await schemaNameAdapter.call(this) }, _after: function (error, result) { if (error) { console.error(error); if (error instanceof Error) { // 如果错误是 Error 实例,则返回包含错误信息的对象 return { errCode: 'error', errMsg: error.message }; } if (error.errCode) { // 如果错误对象中包含 errCode 属性,则直接返回错误对象 return error; } // 抛出其他类型的错误 throw error; } // 返回结果 return result; }, /** * 创建短信任务 * @param {{receiver: *[], type: string}} to * @param {String} to.type=user to.all=true时用来区分发送类型 * @param {Array} to.receiver 用户ID's / 用户标签ID's * @param {Object} to.condition 用户筛选条件 * @param {String} templateId 短信模板ID * @param {Array} templateData 短信模板数据 * @param {Object} options * @param {String} options.taskName 任务名称 */ async createSmsTask(to, templateId, templateData, options = {}) { if (!templateId) { // 如果缺少 templateId,则返回错误信息 return { errCode: errCode('template-id-required'), errMsg: '缺少templateId' }; } if (!to.condition && (!to.receiver || to.receiver.length <= 0)) { // 如果没有预设条件且没有接收者,则返回错误信息 return { errCode: errCode('send-users-is-null'), errMsg: '请选择要发送的用户' }; } const clientInfo = this.getClientInfo(); // 查询短信模板 const { data: templates } = await db.collection(this.tableNames.template).where({ _id: templateId }).get(); if (templates.length <= 0) { // 如果短信模板不存在,则返回错误信息 return { errCode: errCode('template-not-found'), errMsg: '短信模板不存在' }; } const [template] = templates; // 预设条件 if (presetCondition[to.condition]) { to.condition = typeof presetCondition[to.condition] === "function" ? presetCondition[to.condition]() : presetCondition[to.condition]; } // 创建短信任务 const task = await db.collection(this.tableNames.task).add({ app_id: clientInfo.appId, name: options.taskName, template_id: templateId, template_content: template.content, vars: templateData, to, send_qty: 0, success_qty: 0, fail_qty: 0, create_date: Date.now() }); uniSmsCo.createUserSmsMessage(task.id); // 返回任务创建成功的异步结果 return new Promise(resolve => setTimeout(() => resolve({ errCode: 0, errMsg: '任务创建成功', taskId: task.id }), 300)); }, async createUserSmsMessage(taskId, execData = {}) { const parallel = 100 let beforeId const { data: tasks } = await db.collection(this.tableNames.task).where({ _id: taskId }).get() if (tasks.length <= 0) { return { errCode: errCode('task-id-not-found'), errMsg: '任务ID不存在' } } const [task] = tasks let query = { mobile: db.command.exists(true) } // 指定用户发送 if (task.to.type === 'user' && task.to.receiver.length > 0 && !task.to.condition) { let index = 0 if (execData.beforeId) { const i = task.to.receiver.findIndex(id => id === execData.beforeId) index = i !== -1 ? i + 1 : 0 } const receiver = task.to.receiver.slice(index, index + parallel) query._id = db.command.in(receiver) beforeId = receiver[receiver.length - 1] } // 指定用户标签 if (task.to.type === 'userTags') { query.tags = db.command.in(task.to.receiver) } // 自定义条件 if (task.to.condition) { const condition = conditionConvert(task.to.condition, db.command) query = { ...query, ...condition } } if ((task.to.condition || task.to.type === "userTags") && execData.beforeId) { query._id = db.command.gt(execData.beforeId) } // 动态数据仅支持uni-id-users表字段 const dynamicField = parserDynamicField(task.vars) const userFields = dynamicField['uni-id-users'] ? dynamicField['uni-id-users'].reduce((res, field) => { res[field] = true return res }, {}): {} const { data: users } = await db.collection('uni-id-users') .where(query) .field({ mobile: true, ...userFields }) .limit(parallel) .orderBy('_id', 'asc') .get() if (users.length <= 0) { // 更新要发送的短信数量 const count = await db.collection(this.tableNames.log).where({ task_id: taskId }).count() await db.collection(this.tableNames.task).where({ _id: taskId }).update({ send_qty: count.total }) // 开始发送 uniSmsCo.sendSms(taskId) return new Promise(resolve => setTimeout(() => resolve({ errCode: 0, errMsg: '创建完成' }), 500)) } if (!beforeId) { beforeId = users[users.length - 1]._id } let docs = [] for (const user of users) { const varData = await buildTemplateData(task.vars, user) docs.push({ uid: user._id, task_id: taskId, mobile: user.mobile, var_data: varData, status: 0, create_date: Date.now() }) } await db.collection(this.tableNames.log).add(docs) uniSmsCo.createUserSmsMessage(taskId, { beforeId }) return new Promise(resolve => setTimeout(() => resolve(), 500)) }, async sendSms(taskId) { const { data: tasks } = await db.collection(this.tableNames.task).where({ _id: taskId }).get(); if (tasks.length <= 0) { // 如果找不到任务,则输出警告信息并返回 console.warn(`task [${taskId}] not found`); return; } const [task] = tasks; const isStaticTemplate = !task.vars.length; let sendData = { appId: task.app_id, smsKey: smsConfig.smsKey, smsSecret: smsConfig.smsSecret, templateId: task.template_id, data: {} }; const { data: records } = await db.collection(this.tableNames.log) .where({ task_id: taskId, status: 0 }) .limit(isStaticTemplate ? 50 : 1) .field({ mobile: true, var_data: true }) .get(); if (records.length <= 0) { // 如果没有要发送的记录,则返回发送完成的异步结果 return { errCode: 0, errMsg: '发送完成' }; } if (isStaticTemplate) { sendData.phoneList = records.reduce((res, user) => { res.push(user.mobile); return res; }, []); } else { const [record] = records; sendData.phone = record.mobile; sendData.data = record.var_data; } try { // 发送短信 await uniCloud.sendSms(sendData); // 修改发送状态为已发送 await db.collection(this.tableNames.log).where({ _id: db.command.in(records.map(record => record._id)) }).update({ status: 1, send_date: Date.now() }); // 更新任务的短信成功数 await db.collection(this.tableNames.task).where({ _id: taskId }) .update({ success_qty: db.command.inc(records.length) }); } catch (e) { console.error('[sendSms Fail]', e); // 修改发送状态为发送失败 await db.collection(this.tableNames.log).where({ _id: db.command.in(records.map(record => record._id)) }).update({ status: 2, reason: e.errMsg || '未知原因', send_date: Date.now() }); // 更新任务的短信失败数 await db.collection(this.tableNames.task).where({ _id: taskId }) .update({ fail_qty: db.command.inc(records.length) }); } uniSmsCo.sendSms(taskId); return new Promise(resolve => setTimeout(() => resolve(), 500)); }, async template() { const { data: templates = [] } = await db.collection(this.tableNames.template).get(); // 获取所有短信模板 return templates; }, async task(id) { const { data: tasks } = await db.collection(this.tableNames.task).where({ _id: id }).get(); if (tasks.length <= 0) { // 如果找不到任务,则返回 null return null; } // 返回第一个找到的任务 return tasks[0]; }, async updateTemplates(templates) { if (templates.length <= 0) { // 如果模板信息为空,则返回错误 return { errCode: errCode('template-is-null'), errMsg: '缺少模板信息' }; } let group = []; for (const template of templates) { group.push( db.collection(this.tableNames.template).doc(String(template.templateId)).set({ name: template.templateName, content: template.templateContent, type: template.templateType, sign: template.templateSign }) ); } await Promise.all(group); return { errCode: 0, errMsg: '更新成功' }; }, /** * @param to * @param templateId * @param templateData * @param options {Object} * @param options.condition 群发条件 * */ async preview (to, templateId, templateData, options = {}) { const count = 1 let query = { mobile: db.command.exists(true) } // 指定用户发送 if (to.type === 'user' && to.receiver.length > 0 && !to.condition) { // const receiver = to.receiver.slice(0, 10) query._id = db.command.in(to.receiver) } // 指定用户标签 if (to.type === 'userTags') { query.tags = db.command.in(to.receiver) } // 自定义条件 let condition = to.condition if (presetCondition[to.condition]) { condition = typeof presetCondition[to.condition] === "function" ? presetCondition[to.condition]() : presetCondition[to.condition] } if (condition) { query = { ...query, ...conditionConvert(condition, db.command) } } const {data: users} = await db.collection('uni-id-users').where(query).limit(count).get() const {total} = await db.collection('uni-id-users').where(query).count() if (users.length <= 0) { return { errCode: errCode('users-is-null'), errMsg: '请添加要发送的用户' } } const {data: templates} = await db.collection(this.tableNames.template).where({_id: templateId}).get() if (templates.length <= 0) { return { errCode: errCode('template-not-found'), errMsg: '模板不存在' } } const [template] = templates let docs = [] for (const user of users) { const varData = buildTemplateData(templateData, user) const content = template.content.replace(/\$\{(.*?)\}/g, ($1, param) => varData[param] || $1) docs.push(`【${template.sign}】${content}`) } return { errCode: 0, errMsg: '', list: docs, total } } }