123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442 |
- // 云对象教程: 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
- }
- }
- }
|