You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
canteen/uniCloud-aliyun/cloudfunctions/uni-sms-co/index.obj.js

375 lines
10 KiB

// 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
// jsdoc语法提示教程https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129
const createConfig = require('uni-config-center')
const buildTemplateData = require('./build-template-data')
const { parserDynamicField, checkIsStaticTemplate } = require('./utils')
const schemaNameAdapter = require('./schema-name-adapter')
const uniSmsCo = uniCloud.importObject('uni-sms-co')
const db = uniCloud.database()
const smsConfig = createConfig({
pluginId: 'uni-sms-co'
}).config()
function errCode(code) {
return 'uni-sms-co-' + code
}
const tableNames = {
template: 'opendb-sms-template',
task: 'opendb-sms-task',
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) {
if (error instanceof Error) {
return {
errCode: 'error',
errMsg: error.message
}
}
if (error.errCode) {
return error
}
throw error
}
return result
},
/**
* 创建短信任务
* @param {Object} to
* @param {Boolean} to.all=false 全部用户发送
* @param {String} to.type=user to.all=true时用来区分发送类型
* @param {Array} to.receiver 用户ID's / 用户标签ID's
* @param {String} templateId 短信模板ID
* @param {Array} templateData 短信模板数据
* @param {String} options.taskName 任务名称
*/
async createSmsTask(to, templateId, templateData, options = {}) {
if (!templateId) {
return {
errCode: errCode('template-id-required'),
errMsg: '缺少templateId'
}
}
if (!to.all && (!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
// 创建短信任务
const task = await db.collection(this.tableNames.task).add({
app_id: clientInfo.appId,
name: options.taskName,
template_id: templateId,
template_contnet: 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
const query = {
mobile: db.command.exists(true)
}
// 指定用户发送
if (!task.to.all && task.to.type === 'user') {
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.all && 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 sendSms(sendData)
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) {
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: '更新成功'
}
},
async preview (to, templateId, templateData) {
const count = 1
let query = {
mobile: db.command.exists(true)
}
// 指定用户发送
if (!to.all && to.type === 'user') {
const receiver = to.receiver.slice(0, 10)
query._id = db.command.in(receiver)
}
// 指定用户标签
if (to.type === 'userTags') {
query.tags = db.command.in(to.receiver)
}
const {data: users} = await db.collection('uni-id-users').where(query).limit(count).get()
console.log({users, query})
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
}
}
}