payResult.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. /**
  2. * @class ActiveDevices 活跃设备模型 - 每日跑批合并,仅添加本周/本月首次访问的设备。
  3. */
  4. const BaseMod = require('../base')
  5. const Platform = require('../platform')
  6. const Channel = require('../channel')
  7. const Version = require('../version')
  8. const {
  9. DateTime,
  10. UniCrypto
  11. } = require('../../lib')
  12. const dao = require('./dao')
  13. let db = uniCloud.database();
  14. let _ = db.command;
  15. let $ = _.aggregate;
  16. module.exports = class PayResult extends BaseMod {
  17. constructor() {
  18. super()
  19. this.platforms = []
  20. this.channels = []
  21. this.versions = []
  22. }
  23. /**
  24. 支付金额:统计时间内,成功支付的订单金额之和(不剔除退款订单)。
  25. 支付笔数:统计时间内,成功支付的订单数,一个订单对应唯一一个订单号。(不剔除退款订单。)
  26. 支付人数:统计时间内,成功支付的人数(不剔除退款订单)。
  27. 支付设备数:统计时间内,成功支付的设备数(不剔除退款订单)。
  28. 下单金额:统计时间内,成功下单的订单金额(不剔除退款订单)。
  29. 下单笔数:统计时间内,成功下单的订单笔数(不剔除退款订单)。
  30. 下单人数:统计时间内,成功下单的客户数,一人多次下单记为一人(不剔除退款订单)。
  31. 下单设备数:统计时间内,成功下单的设备数,一台设备多次访问被计为一台(不剔除退款订单)。
  32. 访问人数:统计时间内,访问人数,一人多次访问被计为一人(只统计已登录的用户)。
  33. 访问设备数:统计时间内,访问设备数,一台设备多次访问被计为一台(包含未登录的用户)。
  34. * @desc 支付统计(按日统计)
  35. * @param {string} type 统计范围 hour:按小时统计,day:按天统计,week:按周统计,month:按月统计 quarter:按季度统计 year:按年统计
  36. * @param {date|time} date
  37. * @param {bool} reset
  38. */
  39. async stat(type, date, reset, config = {}) {
  40. if (!date) date = Date.now();
  41. // 以下是测试代码-----------------------------------------------------------
  42. //reset = true;
  43. //date = 1667318400000;
  44. // 以上是测试代码-----------------------------------------------------------
  45. let res = await this.run(type, date, reset, config);
  46. if (type === "hour" && config.timely) {
  47. /**
  48. * 如果是小时纬度统计,则还需要再统计(今日实时数据)
  49. * 2022-11-01 01:00:00 统计的是 2022-11-01 的天维度数据(即该天0点-1点数据)
  50. * 2022-11-01 02:00:00 统计的是 2022-11-01 的天维度数据(即该天0点-2点数据)
  51. * 2022-11-01 23:00:00 统计的是 2022-11-01 的天维度数据(即该天0点-23点数据)
  52. * 2022-11-02 00:00:00 统计的是 2022-11-01 的天维度数据(即该天最终数据)
  53. * 2022-11-02 01:00:00 统计的是 2022-11-02 的天维度数据(即该天0点-1点数据)
  54. */
  55. date -= 1000 * 3600; // 需要减去1小时
  56. let tasks = [];
  57. tasks.push(this.run("day", date, true, 0)); // 今日
  58. tasks.push(this.run("week", date, true, 0)); // 本周
  59. tasks.push(this.run("month", date, true, 0)); // 本月
  60. tasks.push(this.run("quarter", date, true, 0)); // 本季度
  61. tasks.push(this.run("year", date, true, 0)); // 本年度
  62. await Promise.all(tasks);
  63. }
  64. return res;
  65. }
  66. /**
  67. * @desc 支付统计
  68. * @param {string} type 统计范围 hour:按小时统计,day:按天统计,week:按周统计,month:按月统计 quarter:按季度统计 year:按年统计
  69. * @param {date|time} date 哪个时间节点计算(默认已当前时间计算)
  70. * @param {bool} reset 如果统计数据已存在,是否需要重新统计
  71. */
  72. async run(type, date, reset, offset = -1) {
  73. let dimension = type;
  74. const dateTime = new DateTime();
  75. // 获取统计的起始时间和截止时间
  76. const dateDimension = dateTime.getTimeDimensionByType(dimension, offset, date);
  77. let start_time = dateDimension.startTime;
  78. let end_time = dateDimension.endTime;
  79. let runStartTime = Date.now();
  80. let debug = true;
  81. if (debug) {
  82. console.log(`-----------------支付统计开始(${dimension})-----------------`);
  83. console.log('本次统计时间:', dateTime.getDate('Y-m-d H:i:s', start_time), "-", dateTime.getDate('Y-m-d H:i:s', end_time))
  84. console.log('本次统计参数:', 'type:' + type, 'date:' + date, 'reset:' + reset)
  85. }
  86. this.startTime = start_time;
  87. let pubWhere = {
  88. start_time,
  89. end_time
  90. };
  91. // 查看当前时间段数据是否已存在,防止重复生成
  92. if (!reset) {
  93. let list = await dao.uniStatPayResult.list({
  94. whereJson: {
  95. ...pubWhere,
  96. dimension
  97. }
  98. });
  99. if (list.length > 0) {
  100. console.log('data have exists')
  101. if (debug) {
  102. let runEndTime = Date.now();
  103. console.log(`耗时:${((runEndTime - runStartTime ) / 1000).toFixed(3)} 秒`)
  104. console.log(`-----------------支付统计结束(${dimension})-----------------`);
  105. }
  106. return {
  107. code: 1003,
  108. msg: 'Pay data in this time have already existed'
  109. }
  110. }
  111. } else {
  112. let delRes = await dao.uniStatPayResult.del({
  113. whereJson: {
  114. ...pubWhere,
  115. dimension
  116. }
  117. });
  118. console.log('Delete old data result:', JSON.stringify(delRes))
  119. }
  120. // 支付订单分组(已下单)
  121. let statPayOrdersList1 = await dao.uniPayOrders.group({
  122. ...pubWhere,
  123. status: "已下单"
  124. });
  125. // 支付订单分组(且已付款,含退款)
  126. let statPayOrdersList2 = await dao.uniPayOrders.group({
  127. ...pubWhere,
  128. status: "已付款"
  129. });
  130. // 支付订单分组(已退款)
  131. let statPayOrdersList3 = await dao.uniPayOrders.group({
  132. ...pubWhere,
  133. status: "已退款"
  134. });
  135. let statPayOrdersList = statPayOrdersList1.concat(statPayOrdersList2).concat(statPayOrdersList3)
  136. let res = {
  137. code: 0,
  138. msg: 'success'
  139. }
  140. // 将支付订单分组查询结果组装
  141. let statDta = {};
  142. if (statPayOrdersList.length > 0) {
  143. for (let i = 0; i < statPayOrdersList.length; i++) {
  144. let item = statPayOrdersList[i];
  145. let {
  146. appid,
  147. version,
  148. platform,
  149. channel,
  150. } = item._id;
  151. let {
  152. status_str
  153. } = item;
  154. let key = `${appid}-${version}-${platform}-${channel}`;
  155. if (!statDta[key]) {
  156. statDta[key] = {
  157. appid,
  158. version,
  159. platform,
  160. channel,
  161. status: {}
  162. };
  163. }
  164. let newItem = JSON.parse(JSON.stringify(item));
  165. delete newItem._id;
  166. statDta[key].status[status_str] = newItem;
  167. }
  168. }
  169. if (this.debug) console.log('statDta: ', statDta)
  170. let saveList = [];
  171. for (let key in statDta) {
  172. let item = statDta[key];
  173. let {
  174. appid,
  175. version,
  176. platform,
  177. channel,
  178. status: statusData,
  179. } = item;
  180. if (!channel) channel = item.scene;
  181. let fieldData = {
  182. pay_total_amount: 0,
  183. pay_order_count: 0,
  184. pay_user_count: 0,
  185. pay_device_count: 0,
  186. create_total_amount: 0,
  187. create_order_count: 0,
  188. create_user_count: 0,
  189. create_device_count: 0,
  190. refund_total_amount: 0,
  191. refund_order_count: 0,
  192. refund_user_count: 0,
  193. refund_device_count: 0,
  194. };
  195. for (let status in statusData) {
  196. let statusItem = statusData[status];
  197. if (status === "已下单") {
  198. // 已下单
  199. fieldData.create_total_amount += statusItem.total_fee;
  200. fieldData.create_order_count += statusItem.order_count;
  201. fieldData.create_user_count += statusItem.user_count;
  202. fieldData.create_device_count += statusItem.device_count;
  203. } else if (status === "已付款") {
  204. // 已付款
  205. fieldData.pay_total_amount += statusItem.total_fee;
  206. fieldData.pay_order_count += statusItem.order_count;
  207. fieldData.pay_user_count += statusItem.user_count;
  208. fieldData.pay_device_count += statusItem.device_count;
  209. } else if (status === "已退款") {
  210. // 已退款
  211. fieldData.refund_total_amount += statusItem.total_fee;
  212. fieldData.refund_order_count += statusItem.order_count;
  213. fieldData.refund_user_count += statusItem.user_count;
  214. fieldData.refund_device_count += statusItem.device_count;
  215. }
  216. }
  217. // 平台信息
  218. let platformInfo = null;
  219. if (this.platforms && this.platforms[platform]) {
  220. // 从缓存中读取数据
  221. platformInfo = this.platforms[platform]
  222. } else {
  223. const platformObj = new Platform()
  224. platformInfo = await platformObj.getPlatformAndCreate(platform, null)
  225. if (!platformInfo || platformInfo.length === 0) {
  226. platformInfo._id = ''
  227. }
  228. this.platforms[platform] = platformInfo;
  229. }
  230. // 渠道信息
  231. let channelInfo = null
  232. const channelKey = appid + '_' + platformInfo._id + '_' + channel;
  233. if (this.channels && this.channels[channelKey]) {
  234. channelInfo = this.channels[channelKey];
  235. } else {
  236. const channelObj = new Channel()
  237. channelInfo = await channelObj.getChannelAndCreate(appid, platformInfo._id, channel)
  238. if (!channelInfo || channelInfo.length === 0) {
  239. channelInfo._id = ''
  240. }
  241. this.channels[channelKey] = channelInfo
  242. }
  243. // 版本信息
  244. let versionInfo = null
  245. const versionKey = appid + '_' + platform + '_' + version
  246. if (this.versions && this.versions[versionKey]) {
  247. versionInfo = this.versions[versionKey]
  248. } else {
  249. const versionObj = new Version()
  250. versionInfo = await versionObj.getVersionAndCreate(appid, platform, version)
  251. if (!versionInfo || versionInfo.length === 0) {
  252. versionInfo._id = ''
  253. }
  254. this.versions[versionKey] = versionInfo
  255. }
  256. let countWhereJson = {
  257. create_time: _.gte(start_time).lte(end_time),
  258. appid,
  259. version,
  260. platform: _.in(getUniPlatform(platform)),
  261. channel,
  262. };
  263. // 活跃设备数量
  264. let activity_device_count = await dao.uniStatSessionLogs.groupCount(countWhereJson);
  265. // 活跃用户数量
  266. let activity_user_count = await dao.uniStatUserSessionLogs.groupCount(countWhereJson);
  267. /*
  268. // TODO 此处有问题,暂不使用
  269. // 新设备数量
  270. let new_device_count = await dao.uniStatSessionLogs.groupCount({
  271. ...countWhereJson,
  272. is_first_visit: 1,
  273. });
  274. // 新注册用户数量
  275. let new_user_count = await dao.uniIdUsers.count({
  276. register_date: _.gte(start_time).lte(end_time),
  277. register_env: {
  278. appid,
  279. app_version: version,
  280. uni_platform: _.in(getUniPlatform(platform)),
  281. channel,
  282. }
  283. });
  284. // 新注册用户中下单的人数
  285. let new_user_create_order_count = await dao.uniIdUsers.countNewUserOrder({
  286. whereJson: {
  287. register_date: _.gte(start_time).lte(end_time),
  288. register_env: {
  289. appid,
  290. app_version: version,
  291. uni_platform: _.in(getUniPlatform(platform)),
  292. channel,
  293. }
  294. },
  295. status: [-1, 0]
  296. });
  297. // 新注册用户中支付成功的人数
  298. let new_user_pay_order_count = await dao.uniIdUsers.countNewUserOrder({
  299. whereJson: {
  300. register_date: _.gte(start_time).lte(end_time),
  301. register_env: {
  302. appid,
  303. app_version: version,
  304. uni_platform: _.in(getUniPlatform(platform)),
  305. channel,
  306. }
  307. },
  308. status: [1, 2, 3]
  309. }); */
  310. saveList.push({
  311. appid,
  312. platform_id: platformInfo._id,
  313. channel_id: channelInfo._id,
  314. version_id: versionInfo._id,
  315. dimension,
  316. create_date: Date.now(), // 记录下当前时间
  317. start_time,
  318. end_time,
  319. stat_date: getNowDate(start_time, 8, dimension),
  320. ...fieldData,
  321. activity_user_count,
  322. activity_device_count,
  323. // new_user_count,
  324. // new_device_count,
  325. // new_user_create_order_count,
  326. // new_user_pay_order_count,
  327. });
  328. }
  329. if (this.debug) console.log('saveList: ', saveList)
  330. //return;
  331. if (saveList.length > 0) {
  332. res = await dao.uniStatPayResult.adds(saveList);
  333. }
  334. if (debug) {
  335. let runEndTime = Date.now();
  336. console.log(`耗时:${((runEndTime - runStartTime ) / 1000).toFixed(3)} 秒`)
  337. console.log(`本次共添加:${saveList.length } 条记录`)
  338. console.log(`-----------------支付统计结束(${dimension})-----------------`);
  339. }
  340. return res
  341. }
  342. }
  343. function getUniPlatform(platform) {
  344. let list = [];
  345. if (["h5", "web"].indexOf(platform) > -1) {
  346. list = ["h5", "web"];
  347. } else if (["app-plus", "app"].indexOf(platform) > -1) {
  348. list = ["app-plus", "app"];
  349. } else {
  350. list = [platform];
  351. }
  352. return list;
  353. }
  354. function getNowDate(date = new Date(), targetTimezone = 8, dimension) {
  355. if (typeof date === "string" && !isNaN(date)) date = Number(date);
  356. if (typeof date == "number") {
  357. if (date.toString().length == 10) date *= 1000;
  358. date = new Date(date);
  359. }
  360. const {
  361. year,
  362. month,
  363. day,
  364. hour,
  365. minute,
  366. second
  367. } = getFullTime(date);
  368. // 现在的时间
  369. let date_str;
  370. if (dimension === "month") {
  371. date_str = timeFormat(date, "yyyy-MM", targetTimezone);
  372. } else if (dimension === "quarter") {
  373. date_str = timeFormat(date, "yyyy-MM", targetTimezone);
  374. } else if (dimension === "year") {
  375. date_str = timeFormat(date, "yyyy", targetTimezone);
  376. } else {
  377. date_str = timeFormat(date, "yyyy-MM-dd", targetTimezone);
  378. }
  379. return {
  380. date_str,
  381. year,
  382. month,
  383. day,
  384. hour,
  385. //minute,
  386. //second,
  387. };
  388. }
  389. function getFullTime(date = new Date(), targetTimezone = 8) {
  390. if (!date) {
  391. return "";
  392. }
  393. if (typeof date === "string" && !isNaN(date)) date = Number(date);
  394. if (typeof date == "number") {
  395. if (date.toString().length == 10) date *= 1000;
  396. date = new Date(date);
  397. }
  398. const dif = date.getTimezoneOffset();
  399. const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
  400. const east8time = date.getTime() + timeDif;
  401. date = new Date(east8time);
  402. let YYYY = date.getFullYear() + '';
  403. let MM = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1);
  404. let DD = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate());
  405. let hh = (date.getHours() < 10 ? '0' + (date.getHours()) : date.getHours());
  406. let mm = (date.getMinutes() < 10 ? '0' + (date.getMinutes()) : date.getMinutes());
  407. let ss = (date.getSeconds() < 10 ? '0' + (date.getSeconds()) : date.getSeconds());
  408. return {
  409. YYYY: Number(YYYY),
  410. MM: Number(MM),
  411. DD: Number(DD),
  412. hh: Number(hh),
  413. mm: Number(mm),
  414. ss: Number(ss),
  415. year: Number(YYYY),
  416. month: Number(MM),
  417. day: Number(DD),
  418. hour: Number(hh),
  419. minute: Number(mm),
  420. second: Number(ss),
  421. };
  422. };
  423. /**
  424. * 日期格式化
  425. */
  426. function timeFormat(time, fmt = 'yyyy-MM-dd hh:mm:ss', targetTimezone = 8) {
  427. try {
  428. if (!time) {
  429. return "";
  430. }
  431. if (typeof time === "string" && !isNaN(time)) time = Number(time);
  432. // 其他更多是格式化有如下:
  433. // yyyy-MM-dd hh:mm:ss|yyyy年MM月dd日 hh时MM分等,可自定义组合
  434. let date;
  435. if (typeof time === "number") {
  436. if (time.toString().length == 10) time *= 1000;
  437. date = new Date(time);
  438. } else {
  439. date = time;
  440. }
  441. const dif = date.getTimezoneOffset();
  442. const timeDif = dif * 60 * 1000 + (targetTimezone * 60 * 60 * 1000);
  443. const east8time = date.getTime() + timeDif;
  444. date = new Date(east8time);
  445. let opt = {
  446. "M+": date.getMonth() + 1, //月份
  447. "d+": date.getDate(), //日
  448. "h+": date.getHours(), //小时
  449. "m+": date.getMinutes(), //分
  450. "s+": date.getSeconds(), //秒
  451. "q+": Math.floor((date.getMonth() + 3) / 3), //季度
  452. "S": date.getMilliseconds() //毫秒
  453. };
  454. if (/(y+)/.test(fmt)) {
  455. fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
  456. }
  457. for (let k in opt) {
  458. if (new RegExp("(" + k + ")").test(fmt)) {
  459. fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (opt[k]) : (("00" + opt[k]).substr(("" + opt[k]).length)));
  460. }
  461. }
  462. return fmt;
  463. } catch (err) {
  464. // 若格式错误,则原值显示
  465. return time;
  466. }
  467. };