Initial commit
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
module.exports = (templateData, user) => {
|
||||
const data = {}
|
||||
for (const template of templateData) {
|
||||
const isDynamic = /\{.*?\}/.test(template.value)
|
||||
|
||||
// 仅支持uni-id-users
|
||||
if (isDynamic) {
|
||||
const [collection, field] = template.value.replace(/\{|\}/g, '').split('.')
|
||||
data[template.field] = collection === 'uni-id-users' ? user[field] || template.value: template.value
|
||||
} else {
|
||||
data[template.field] = template.value
|
||||
}
|
||||
// switch (template.type) {
|
||||
// case 'static':
|
||||
// data[template.field] = template.value
|
||||
// break
|
||||
// case 'dynamic':
|
||||
// data[template.field] = user[template.value] || ''
|
||||
// break
|
||||
// default:
|
||||
// throw new Error(`template type [${template.type}] not supported`)
|
||||
// }
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
374
uniCloud-alipay/cloudfunctions/uni-sms-co/index.obj.js
Normal file
374
uniCloud-alipay/cloudfunctions/uni-sms-co/index.obj.js
Normal file
@@ -0,0 +1,374 @@
|
||||
// 云对象教程: 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
|
||||
}
|
||||
}
|
||||
}
|
||||
14
uniCloud-alipay/cloudfunctions/uni-sms-co/package.json
Normal file
14
uniCloud-alipay/cloudfunctions/uni-sms-co/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "uni-sms-co",
|
||||
"dependencies": {
|
||||
"uni-config-center": "file:..\/..\/..\/uni_modules\/uni-config-center\/uniCloud\/cloudfunctions\/common\/uni-config-center"
|
||||
},
|
||||
"extensions": {
|
||||
"uni-cloud-sms": {},
|
||||
"uni-cloud-jql": {}
|
||||
},
|
||||
"origin-plugin-dev-name": "uni-starter",
|
||||
"origin-plugin-version": "2.2.8",
|
||||
"plugin-dev-name": "uni-starter",
|
||||
"plugin-version": "2.2.8"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
const db = uniCloud.database()
|
||||
|
||||
module.exports = async function () {
|
||||
try {
|
||||
const count = await db.collection('batch-sms-template').count()
|
||||
|
||||
if (count.total > 0) {
|
||||
this.tableNames = {
|
||||
template: 'batch-sms-template',
|
||||
task: 'batch-sms-task',
|
||||
log: 'batch-sms-result'
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
42
uniCloud-alipay/cloudfunctions/uni-sms-co/utils.js
Normal file
42
uniCloud-alipay/cloudfunctions/uni-sms-co/utils.js
Normal file
@@ -0,0 +1,42 @@
|
||||
exports.chunk = function (arr, num) {
|
||||
const list = []
|
||||
let current = []
|
||||
|
||||
for (const item of arr) {
|
||||
current.push(item);
|
||||
if (current.length === num) {
|
||||
list.push(current)
|
||||
current = []
|
||||
}
|
||||
}
|
||||
|
||||
if (current.length) list.push(current)
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
exports.checkIsStaticTemplate = function (data = []) {
|
||||
let isStatic = data.length <= 0
|
||||
|
||||
for (const template of data) {
|
||||
if (template.type === 'static') {
|
||||
isStatic = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return isStatic
|
||||
}
|
||||
|
||||
exports.parserDynamicField = function (templateData) {
|
||||
return templateData.reduce((res, template) => {
|
||||
if (/\{.*?\}/.test(template.value)) {
|
||||
const [collection, field] = template.value.replace(/\{|\}/g, '').split('.')
|
||||
if (!res[collection]) {
|
||||
res[collection] = []
|
||||
}
|
||||
res[collection].push(field)
|
||||
}
|
||||
return res
|
||||
}, {})
|
||||
}
|
||||
Reference in New Issue
Block a user