index.obj.js 13 KB

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