index.obj.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. // 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj
  2. // jsdoc语法提示教程:https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/129
  3. const createConfig = require('uni-config-center')
  4. const buildTemplateData = require('./build-template-data')
  5. const { parserDynamicField, checkIsStaticTemplate } = require('./utils')
  6. const schemaNameAdapter = require('./schema-name-adapter')
  7. const uniSmsCo = uniCloud.importObject('uni-sms-co')
  8. const db = uniCloud.database()
  9. const smsConfig = createConfig({
  10. pluginId: 'uni-sms-co'
  11. }).config()
  12. function errCode(code) {
  13. return 'uni-sms-co-' + code
  14. }
  15. const tableNames = {
  16. template: 'opendb-sms-template',
  17. task: 'opendb-sms-task',
  18. log: 'opendb-sms-log'
  19. }
  20. module.exports = {
  21. _before: async function () { // 通用预处理器
  22. if (!smsConfig.smsKey || smsConfig.smsKey.length <= 20 || !smsConfig.smsSecret || smsConfig.smsSecret.length <= 20) {
  23. throw new Error('请先配置smsKey和smsSecret')
  24. }
  25. this.tableNames = tableNames
  26. /**
  27. * 优化 schema 的命名规范,需要兼容 uni-admin@2.1.6 以下版本
  28. * 如果是在uni-admin@2.1.6版本以上创建的项目可以将其注释
  29. * */
  30. await schemaNameAdapter.call(this)
  31. },
  32. _after: function (error, result) {
  33. if (error) {
  34. if (error instanceof Error) {
  35. return {
  36. errCode: 'error',
  37. errMsg: error.message
  38. }
  39. }
  40. if (error.errCode) {
  41. return error
  42. }
  43. throw error
  44. }
  45. return result
  46. },
  47. /**
  48. * 创建短信任务
  49. * @param {Object} to
  50. * @param {Boolean} to.all=false 全部用户发送
  51. * @param {String} to.type=user to.all=true时用来区分发送类型
  52. * @param {Array} to.receiver 用户ID's / 用户标签ID's
  53. * @param {String} templateId 短信模板ID
  54. * @param {Array} templateData 短信模板数据
  55. * @param {String} options.taskName 任务名称
  56. */
  57. async createSmsTask(to, templateId, templateData, options = {}) {
  58. if (!templateId) {
  59. return {
  60. errCode: errCode('template-id-required'),
  61. errMsg: '缺少templateId'
  62. }
  63. }
  64. if (!to.all && (!to.receiver || to.receiver.length <= 0)) {
  65. return {
  66. errCode: errCode('send-users-is-null'),
  67. errMsg: '请选择要发送的用户'
  68. }
  69. }
  70. const clientInfo = this.getClientInfo()
  71. const {data: templates} = await db.collection(this.tableNames.template).where({_id: templateId}).get()
  72. if (templates.length <= 0) {
  73. return {
  74. errCode: errCode('template-not-found'),
  75. errMsg: '短信模板不存在'
  76. }
  77. }
  78. const [template] = templates
  79. // 创建短信任务
  80. const task = await db.collection(this.tableNames.task).add({
  81. app_id: clientInfo.appId,
  82. name: options.taskName,
  83. template_id: templateId,
  84. template_contnet: template.content,
  85. vars: templateData,
  86. to,
  87. send_qty: 0,
  88. success_qty: 0,
  89. fail_qty: 0,
  90. create_date: Date.now()
  91. })
  92. uniSmsCo.createUserSmsMessage(task.id)
  93. return new Promise(resolve => setTimeout(() => resolve({
  94. errCode: 0,
  95. errMsg: '任务创建成功',
  96. taskId: task.id
  97. }), 300))
  98. },
  99. async createUserSmsMessage(taskId, execData = {}) {
  100. const parallel = 100
  101. let beforeId
  102. const { data: tasks } = await db.collection(this.tableNames.task).where({ _id: taskId }).get()
  103. if (tasks.length <= 0) {
  104. return {
  105. errCode: errCode('task-id-not-found'),
  106. errMsg: '任务ID不存在'
  107. }
  108. }
  109. const [task] = tasks
  110. const query = {
  111. mobile: db.command.exists(true)
  112. }
  113. // 指定用户发送
  114. if (!task.to.all && task.to.type === 'user') {
  115. let index = 0
  116. if (execData.beforeId) {
  117. const i = task.to.receiver.findIndex(id => id === execData.beforeId)
  118. index = i !== -1 ? i + 1 : 0
  119. }
  120. const receiver = task.to.receiver.slice(index, index + parallel)
  121. query._id = db.command.in(receiver)
  122. beforeId = receiver[receiver.length - 1]
  123. }
  124. // 指定用户标签
  125. if (task.to.type === 'userTags') {
  126. query.tags = db.command.in(task.to.receiver)
  127. }
  128. // 全部用户
  129. if (task.to.all && execData.beforeId) {
  130. query._id = db.command.gt(execData.beforeId)
  131. }
  132. // 动态数据仅支持uni-id-users表字段
  133. const dynamicField = parserDynamicField(task.vars)
  134. const userFields = dynamicField['uni-id-users'] ? dynamicField['uni-id-users'].reduce((res, field) => {
  135. res[field] = true
  136. return res
  137. }, {}): {}
  138. const { data: users } = await db.collection('uni-id-users')
  139. .where(query)
  140. .field({
  141. mobile: true,
  142. ...userFields
  143. })
  144. .limit(parallel)
  145. .orderBy('_id', 'asc')
  146. .get()
  147. if (users.length <= 0) {
  148. // 更新要发送的短信数量
  149. const count = await db.collection(this.tableNames.log).where({ task_id: taskId }).count()
  150. await db.collection(this.tableNames.task).where({ _id: taskId }).update({
  151. send_qty: count.total
  152. })
  153. // 开始发送
  154. uniSmsCo.sendSms(taskId)
  155. return new Promise(resolve => setTimeout(() => resolve({
  156. errCode: 0,
  157. errMsg: '创建完成'
  158. }), 500))
  159. }
  160. if (!beforeId) {
  161. beforeId = users[users.length - 1]._id
  162. }
  163. let docs = []
  164. for (const user of users) {
  165. const varData = await buildTemplateData(task.vars, user)
  166. docs.push({
  167. uid: user._id,
  168. task_id: taskId,
  169. mobile: user.mobile,
  170. var_data: varData,
  171. status: 0,
  172. create_date: Date.now()
  173. })
  174. }
  175. await db.collection(this.tableNames.log).add(docs)
  176. uniSmsCo.createUserSmsMessage(taskId, { beforeId })
  177. return new Promise(resolve => setTimeout(() => resolve(), 500))
  178. },
  179. async sendSms(taskId) {
  180. const { data: tasks } = await db.collection(this.tableNames.task).where({ _id: taskId }).get()
  181. if (tasks.length <= 0) {
  182. console.warn(`task [${taskId}] not found`)
  183. return
  184. }
  185. const [task] = tasks
  186. const isStaticTemplate = !task.vars.length
  187. let sendData = {
  188. appId: task.app_id,
  189. smsKey: smsConfig.smsKey,
  190. smsSecret: smsConfig.smsSecret,
  191. templateId: task.template_id,
  192. data: {}
  193. }
  194. const { data: records } = await db.collection(this.tableNames.log)
  195. .where({ task_id: taskId, status: 0 })
  196. .limit(isStaticTemplate ? 50 : 1)
  197. .field({ mobile: true, var_data: true })
  198. .get()
  199. if (records.length <= 0) {
  200. return {
  201. errCode: 0,
  202. errMsg: '发送完成'
  203. }
  204. }
  205. if (isStaticTemplate) {
  206. sendData.phoneList = records.reduce((res, user) => {
  207. res.push(user.mobile)
  208. return res
  209. }, [])
  210. } else {
  211. const [record] = records
  212. sendData.phone = record.mobile
  213. sendData.data = record.var_data
  214. }
  215. try {
  216. // await sendSms(sendData)
  217. await uniCloud.sendSms(sendData)
  218. // 修改发送状态为已发送
  219. await db.collection(this.tableNames.log).where({
  220. _id: db.command.in(records.map(record => record._id))
  221. }).update({
  222. status: 1,
  223. send_date: Date.now()
  224. })
  225. // 更新任务的短信成功数
  226. await db.collection(this.tableNames.task).where({ _id: taskId })
  227. .update({
  228. success_qty: db.command.inc(records.length)
  229. })
  230. } catch (e) {
  231. console.error('[sendSms Fail]', e)
  232. // 修改发送状态为发送失败
  233. await db.collection(this.tableNames.log).where({
  234. _id: db.command.in(records.map(record => record._id))
  235. }).update({
  236. status: 2,
  237. reason: e.errMsg || '未知原因',
  238. send_date: Date.now()
  239. })
  240. // 更新任务的短信失败数
  241. await db.collection(this.tableNames.task).where({ _id: taskId })
  242. .update({
  243. fail_qty: db.command.inc(records.length)
  244. })
  245. }
  246. uniSmsCo.sendSms(taskId)
  247. return new Promise(resolve => setTimeout(() => resolve(), 500))
  248. },
  249. async template() {
  250. const {data: templates = []} = await db.collection(this.tableNames.template).get()
  251. return templates
  252. },
  253. async task (id) {
  254. const {data: tasks} = await db.collection(this.tableNames.task).where({_id: id}).get()
  255. if (tasks.length <= 0) {
  256. return null
  257. }
  258. return tasks[0]
  259. },
  260. async updateTemplates (templates) {
  261. if (templates.length <= 0) {
  262. return {
  263. errCode: errCode('template-is-null'),
  264. errMsg: '缺少模板信息'
  265. }
  266. }
  267. let group = []
  268. for (const template of templates) {
  269. group.push(
  270. db.collection(this.tableNames.template).doc(String(template.templateId)).set({
  271. name: template.templateName,
  272. content: template.templateContent,
  273. type: template.templateType,
  274. sign: template.templateSign
  275. })
  276. )
  277. }
  278. await Promise.all(group)
  279. return {
  280. errCode: 0,
  281. errMsg: '更新成功'
  282. }
  283. },
  284. async preview (to, templateId, templateData) {
  285. const count = 1
  286. let query = {
  287. mobile: db.command.exists(true)
  288. }
  289. // 指定用户发送
  290. if (!to.all && to.type === 'user') {
  291. const receiver = to.receiver.slice(0, 10)
  292. query._id = db.command.in(receiver)
  293. }
  294. // 指定用户标签
  295. if (to.type === 'userTags') {
  296. query.tags = db.command.in(to.receiver)
  297. }
  298. const {data: users} = await db.collection('uni-id-users').where(query).limit(count).get()
  299. console.log({users, query})
  300. if (users.length <= 0) {
  301. return {
  302. errCode: errCode('users-is-null'),
  303. errMsg: '请添加要发送的用户'
  304. }
  305. }
  306. const {data: templates} = await db.collection(this.tableNames.template).where({_id: templateId}).get()
  307. if (templates.length <= 0) {
  308. return {
  309. errCode: errCode('template-not-found'),
  310. errMsg: '模板不存在'
  311. }
  312. }
  313. const [template] = templates
  314. let docs = []
  315. for (const user of users) {
  316. const varData = buildTemplateData(templateData, user)
  317. const content = template.content.replace(/\$\{(.*?)\}/g, ($1, param) => varData[param] || $1)
  318. docs.push(`【${template.sign}】${content}`)
  319. }
  320. return {
  321. errCode: 0,
  322. errMsg: '',
  323. list: docs
  324. }
  325. }
  326. }