index.obj.js 12 KB

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