小说推广数据系统

PlayletService.php 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. <?php
  2. namespace App\Services;
  3. use App\Log;
  4. use App\Models\AccountPromoteConfig;
  5. use App\Models\AdsReport;
  6. use App\Models\PlayletStatisticsData;
  7. use App\Models\VpAccount;
  8. use App\Models\VpOrder;
  9. use App\RedisModel;
  10. class PlayletService
  11. {
  12. const DAYS = 100;
  13. const ACTIVATE_FANS_DAYS = 100;
  14. const PAID_FANS_DAYS = 100;
  15. const FANS_ACTIVE = 'NOVEL:VpFansActive_';
  16. const CELL_LABEL = array('A','B','C','D','E','F','G','H','I','J','K', 'L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK', 'AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ','BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK', 'BL','BM','BN','BO','BP','BQ','BR','BS','BT','BU','BV','BW','BX','BY','BZ','CA','CB','CC','CD','CE','CF','CG','CH','CI','CJ','CK','CL','CM','CN','CO','CP','CQ','CR','CS','CT','CU','CV','CW','CX','CY','CZ','DA','DB','DC','DD','DE','DF','DG','DH','DI','DJ','DK', 'DL','DM','DN','DO','DP','DQ','DR','DS','DT','DU','DV','DW','DX','DY','DZ');
  17. /**
  18. * 短剧数据趋势
  19. * @param $keyword string 公众号昵称关键词
  20. * @param $startDate string 查询起始日期
  21. * @param $endDate string 查询截止日期
  22. * @param $page integer 当前页码数
  23. * @param $pageSize integer 每页显示条数
  24. * */
  25. public static function dataTrend($keyword, $startDate, $endDate, $page, $pageSize)
  26. {
  27. $appIdLists = false;
  28. if(!empty($keyword)) {
  29. # 获取关键词对应的公众号AppId
  30. // $appIdLists = VpAccount::getAccountList(null, ['app_id'], ['nickname'=>$keyword, 'platform_id' => 1]);
  31. $appIdLists = VpAccount::getAccountList(null, ['app_id'], ['nickname'=>$keyword]);
  32. }
  33. # 获取不同时间段内新增的粉丝充值信息
  34. list($fansData, $total) = VpOrder::dataTrend($appIdLists, $startDate, $endDate, $page, $pageSize);
  35. # 获取公众号数据
  36. $appIds = $fansData->pluck('app_id');
  37. $accountList = VpAccount::selectRaw("distinct(app_id) as app_id, nickname")->whereIn('app_id', $appIds)->get();
  38. # 获取列表内的日期
  39. $dateList = $fansData->pluck('subscribed_date');
  40. # 获取各日期对应的花费
  41. $paidList = AdsReport::getPaidData($appIds, $dateList);
  42. foreach($fansData as $trend) {
  43. $trend->charge_total = $trend->charge_total / 10000;
  44. # 公众号信息
  45. $accountInfo = $accountList->where('app_id', $trend->app_id)->first();
  46. $trend->name = isset($accountInfo->nickname) ? $accountInfo->nickname : '';
  47. # 当日消耗
  48. $paidInfo = $paidList->where('app_id', $trend->app_id)
  49. ->where('ref_date', $trend->subscribed_date)
  50. ->first();
  51. $trend->day_paid = isset($paidInfo->paid) ? ($paidInfo->paid / 100) : 0;
  52. # 获取当日新用户充值情况
  53. $newUserCharge = VpOrder::getNewUserChargeFirstDay($trend->subscribed_date, $trend->app_id);
  54. $trend->new_user_charge = isset($newUserCharge->new_user_charge) ? ($newUserCharge->new_user_charge / 10000): 0;
  55. $trend->new_user_charge_uv = isset($newUserCharge->new_user_charge_uv) ? $newUserCharge->new_user_charge_uv : 0;
  56. $trend->new_user_charge_pv = isset($newUserCharge->new_user_charge_pv) ? $newUserCharge->new_user_charge_pv : 0;
  57. # 首日ROI
  58. $trend->first_day_roi = $trend->day_paid != 0 ? ($trend->new_user_charge / $trend->day_paid) : 0;
  59. # 企微关注数
  60. $trend->scan_follow_count = isset($paidInfo->scan_follow_count) ? $paidInfo->scan_follow_count : 0;
  61. # 企微关注成本
  62. $trend->follow_cost = $trend->scan_follow_count > 0 ? round($trend->day_paid / $trend->scan_follow_count, 3) : 0;
  63. # 新增粉丝数
  64. $trend->fans_increase = isset($paidInfo->follow_uv) ? $paidInfo->follow_uv : 0;
  65. # 首日用户成本
  66. $trend->charge_user_cost = $trend->new_user_charge_uv != 0 ? ($trend->day_paid / $trend->new_user_charge_uv) : 0;
  67. # 回本率
  68. $trend->cost_cover_rate = $trend->day_paid != 0 ? ($trend->charge_total / $trend->day_paid) : 0;
  69. # 获取当日关注用户的累计激活数据
  70. $newUserChargeCollect = VpOrder::getNewUserCharge($trend->subscribed_date, $trend->app_id);
  71. $trend->new_user_charge_total = isset($newUserChargeCollect->new_user_charge) ? $newUserChargeCollect->new_user_charge / 10000 : 0;
  72. $trend->new_user_charge_uv_count = isset($newUserChargeCollect->new_user_charge_uv) ? $newUserChargeCollect->new_user_charge_uv : 0;
  73. $trend->new_user_charge_pv_count = isset($newUserChargeCollect->new_user_charge_pv) ? $newUserChargeCollect->new_user_charge_pv : 0;
  74. // 充增回倍
  75. $trend->chargeData = self::chargeAfterSubscribed(
  76. $trend->subscribed_date, self::DAYS, $trend->app_id, $trend->first_day_roi, $trend->day_paid
  77. );
  78. }
  79. return [$fansData, $total];
  80. }
  81. /**
  82. * 短剧粉丝激活趋势
  83. * @param $keyword string 公众号昵称关键词
  84. * @param $startDate string 查询起始日期
  85. * @param $endDate string 查询截止日期
  86. * @param $page integer 当前页码数
  87. * @param $pageSize integer 每页显示条数
  88. * */
  89. public static function activateTrend($keyword, $startDate, $endDate, $page, $pageSize)
  90. {
  91. $appIdLists = false;
  92. if(!empty($keyword)) {
  93. # 获取关键词对应的公众号AppId
  94. $appIdLists = VpAccount::getAccountList(null, ['app_id'], ['nickname'=>$keyword]);
  95. }
  96. # 获取日期范围内充值信息
  97. list($activateData, $total) = VpOrder::subscribedFansData($appIdLists, $startDate, $endDate, $page, $pageSize);
  98. # 获取公众号数据
  99. $appIds = $activateData->pluck('app_id');
  100. $accountList = VpAccount::selectRaw("distinct(app_id) as app_id, nickname")->whereIn('app_id', $appIds)->get();
  101. # 获取列表内的日期
  102. $dateList = $activateData->pluck('subscribed_date');
  103. # 获取各日期对应的花费
  104. $paidList = AdsReport::getPaidData($appIds, $dateList);
  105. foreach ($activateData as $paid) {
  106. $paid->charge_total = $paid->charge_total / 10000;
  107. # 公众号信息
  108. $accountInfo = $accountList->where('app_id', $paid->app_id)->first();
  109. $paid->name = isset($accountInfo->nickname) ? $accountInfo->nickname : '';
  110. # 当日消耗
  111. $paidInfo = $paidList->where('app_id', $paid->app_id)
  112. ->where('ref_date', $paid->subscribed_date)
  113. ->first();
  114. $paid->day_paid = isset($paidInfo->paid) ? ($paidInfo->paid / 100) : 0;
  115. # 回本率
  116. $paid->cost_cover_rate = $paid->day_paid != 0 ? ($paid->charge_total / $paid->day_paid) : 0;
  117. # 获取当日新用户累计充值情况
  118. $newUserCharge = VpOrder::getNewUserCharge($paid->subscribed_date, $paid->app_id);
  119. $paid->new_user_charge = isset($newUserCharge->new_user_charge) ? $newUserCharge->new_user_charge / 10000 : 0;
  120. $paid->new_user_charge_uv = isset($newUserCharge->new_user_charge_uv) ? $newUserCharge->new_user_charge_uv : 0;
  121. $paid->new_user_charge_pv = isset($newUserCharge->new_user_charge_pv) ? $newUserCharge->new_user_charge_pv : 0;
  122. # 企微关注数
  123. $paid->scan_follow_count = isset($paidInfo->scan_follow_count) ? $paidInfo->scan_follow_count : 0;
  124. # 企微关注成本
  125. $paid->follow_cost = $paid->scan_follow_count > 0 ? round($paid->day_paid / $paid->scan_follow_count, 3) : 0;
  126. # 总充值数据
  127. $paid->charge_uv_all = VpOrder::getChargeUvOfTime($paid->app_id, $paid->subscribed_date, date("Y-m-d"));
  128. # 首日用户成本
  129. $paid->charge_user_cost = $paid->new_user_charge_uv != 0 ? ($paid->day_paid / $paid->new_user_charge_uv) : 0;
  130. # D1~Dn人数、成本、占比
  131. $paid->activeData = self::activeAfterSubscribed(
  132. $paid->subscribed_date, self::ACTIVATE_FANS_DAYS, $paid->app_id, $paid->day_paid
  133. );
  134. # 计算累计充值人数
  135. $paid->active_fans_total = collect($paid->activeData)->sum('fans');
  136. # 累计用户成本
  137. $paid->charge_user_cost_all = $paid->active_fans_total != 0 ? ($paid->day_paid / $paid->active_fans_total) : 0;
  138. }
  139. return [$activateData, $total];
  140. }
  141. /**
  142. * 计算某账号某日新增充值粉丝在后N天的充值数据
  143. * @param $paid float 关注当日消耗
  144. * @param $subscribedAt string 关注日期
  145. * @param $days int 关注后的后几日数据
  146. * @param $appId string 公众号AppId
  147. * @return array
  148. * */
  149. public static function activeAfterSubscribed($subscribedAt, $days, $appId, $paid)
  150. {
  151. # 校验是否是日期以及$days的合法性
  152. if((date("Y-m-d", strtotime($subscribedAt)) != $subscribedAt) || !is_int($days)) {
  153. Log::logError('日期格式非法',[
  154. 'subscribedAt' => $subscribedAt,
  155. 'days' => $days,
  156. ], 'ActiveAfterSubscribed');
  157. return [];
  158. }
  159. # 日期处理
  160. $expiryAt = date("Y-m-d", strtotime('+'.$days. ' days', strtotime($subscribedAt)));
  161. if($expiryAt >date("Y-m-d")) $expiryAt = date("Y-m-d");
  162. $activeData = RedisModel::getAfterDecode(self::FANS_ACTIVE . $appId . '_' . $subscribedAt . '_' . $expiryAt);
  163. if(empty($chargeData)) {
  164. # 首次充值数据
  165. $data = VpOrder::getActiveAfterSubscribed($appId, $subscribedAt, $expiryAt, $subscribedAt);
  166. # 获取后N天每天的激活人数
  167. $activeData = [];
  168. $activeFansTotal = 0;
  169. for($i=0; $i<$days; $i++) {
  170. $refDate = date("Y-m-d", strtotime('+'.$i. ' days', strtotime($subscribedAt)));
  171. if($refDate>date("Y-m-d")) {
  172. $activeData[$i] = [];
  173. continue;
  174. }
  175. # 第N天的激活人数
  176. $activeFans = $data->where('paid_at', '>=', $refDate . ' 00:00:00')
  177. ->where('paid_at', '<=', $refDate . ' 23:59:59')
  178. ->count();
  179. $activeFansTotal += $activeFans;
  180. $activeData[$i] = [
  181. 'fans' => $activeFans,
  182. 'cost' => $activeFansTotal != 0 ? ($paid / $activeFansTotal) : 0,
  183. ];
  184. }
  185. // RedisModel::setAfterEncode(self::FANS_ACTIVE . $appId . '_' . $subscribedAt . '_' . $expiryAt, $activeData);
  186. // RedisModel::expire(self::FANS_ACTIVE . $appId . '_' . $subscribedAt . '_' . $expiryAt, 86400);
  187. }
  188. return $activeData;
  189. }
  190. /**
  191. * 计算某账号某日新增充值粉丝在后N天的充值数据
  192. * @param $paid float 关注当日消耗
  193. * @param $subscribedAt string 关注日期
  194. * @param $days int 关注后的后几日数据
  195. * @param $appId string 公众号AppId
  196. * @param $roi string 首日ROI
  197. * @return array
  198. * */
  199. public static function chargeAfterSubscribed($subscribedAt, $days, $appId, $roi, $paid)
  200. {
  201. # 校验是否是日期以及$days的合法性
  202. if((date("Y-m-d", strtotime($subscribedAt)) != $subscribedAt) || !is_int($days)) {
  203. Log::logError('日期格式非法',[
  204. 'subscribedAt' => $subscribedAt,
  205. 'days' => $days,
  206. 'appId' => $appId
  207. ], 'ChargeAfterSubscribed');
  208. return [];
  209. }
  210. # 日期处理
  211. $expiryAt = date("Y-m-d", strtotime('+'.$days. ' days', strtotime($subscribedAt)));
  212. if($expiryAt >date("Y-m-d")) $expiryAt = date("Y-m-d");
  213. // $chargeData = RedisModel::getAfterDecode(self::CHARGE_PAID . $appId . '_' . $subscribedAt . '_' . $expiryAt);
  214. $chargeData = [];
  215. if(empty($chargeData)) {
  216. # 充值数据
  217. $data = VpOrder::getChargeAfterSubscribed($appId, $subscribedAt, $expiryAt, $subscribedAt);
  218. # 获取后N天每天的充值数据及截止第N天的统计数据
  219. $chargeData = [];
  220. $chargePriceTotal = 0;
  221. for($i=0; $i<$days; $i++) {
  222. $refDate = date("Y-m-d", strtotime('+'.$i. ' days', strtotime($subscribedAt)));
  223. if($refDate>date("Y-m-d")) {
  224. $chargeData[$i] = [];
  225. continue;
  226. }
  227. # 第N天的充值数据
  228. $chargePrice = $data->where('paid_at', '>=', $refDate . ' 00:00:00')
  229. ->where('paid_at', '<=', $refDate . ' 23:59:59')
  230. ->sum('price');
  231. $chargePrice = $chargePrice / 10000;
  232. $chargePriceTotal += $chargePrice;
  233. $chargeData[$i] = [
  234. 'day_charge' => $chargePrice,
  235. 'charge_total' => $chargePriceTotal,
  236. 'day_paid' => $paid,
  237. 'day_add' => $paid != 0 ? ($chargePrice / $paid) : 0,
  238. 'day_cover' => $paid != 0 ? ($chargePriceTotal / $paid) : 0,
  239. 'day_times' => ($paid * $roi) != 0 ? ($chargePriceTotal / ($paid * $roi)) : 0
  240. ];
  241. }
  242. }
  243. return $chargeData;
  244. }
  245. public static function playletDataTotal($playletId, $page, $pageSize, $minDayPaid, $maxDayPaid)
  246. {
  247. $offset = ($page - 1) * $pageSize;
  248. $date = date('Y-m-d');
  249. $playletList = PlayletStatisticsData::where(function($query) use ($playletId, $minDayPaid, $maxDayPaid) {
  250. if($playletId) $query->where('playlet_id', $playletId);
  251. if($minDayPaid) $query->where('day_paid', '>=', $minDayPaid);
  252. if($maxDayPaid) $query->where('day_paid', '<=', $maxDayPaid);
  253. })->where('created_date', $date)->orderBy('start_date', 'desc')->offset($offset)->limit($pageSize)->get();
  254. $playletCount = PlayletStatisticsData::where(function($query) use ($playletId, $minDayPaid, $maxDayPaid) {
  255. if($playletId) $query->where('playlet_id', $playletId);
  256. if($minDayPaid) $query->where('day_paid', '>=', $minDayPaid);
  257. if($maxDayPaid) $query->where('day_paid', '<=', $maxDayPaid);
  258. })->where('created_date', $date)->count();
  259. foreach($playletList as $playlet) {
  260. $playlet->charge_data = json_decode($playlet->charge_data, 1);
  261. $playlet->subscribe_date = $playlet->start_date.'~'.$playlet->end_date;
  262. }
  263. return [$playletList, $playletCount];
  264. }
  265. public static function playletData($playletId, $startDate, $endDate, $page, $pageSize, $minDayPaid, $maxDayPaid)
  266. {
  267. $offset = ($page - 1) * $pageSize;
  268. $date = date('Y-m-d');
  269. $playletList = PlayletStatisticsData::where(function($query) use ($startDate, $endDate, $playletId, $minDayPaid, $maxDayPaid) {
  270. if($playletId) $query->where('playlet_id', $playletId);
  271. if($endDate) $query->where('start_date', '<=', $endDate);
  272. if($startDate) $query->where('start_date', '>=', $startDate);
  273. if($minDayPaid) $query->where('day_paid', '>=', $minDayPaid);
  274. if($maxDayPaid) $query->where('day_paid', '<=', $maxDayPaid);
  275. })->where('created_date', $date)->orderBy('start_date', 'desc')->offset($offset)->limit($pageSize)->get();
  276. $playletCount = PlayletStatisticsData::where(function($query) use ($startDate, $endDate, $playletId, $minDayPaid, $maxDayPaid) {
  277. if($playletId) $query->where('playlet_id', $playletId);
  278. if($endDate) $query->where('start_date', '<=', $endDate);
  279. if($startDate) $query->where('start_date', '>=', $startDate);
  280. if($minDayPaid) $query->where('day_paid', '>=', $minDayPaid);
  281. if($maxDayPaid) $query->where('day_paid', '<=', $maxDayPaid);
  282. })->where('created_date', $date)->count();
  283. foreach($playletList as $playlet) {
  284. $playlet->charge_data = json_decode($playlet->charge_data, 1);
  285. $playlet->subscribe_date = $playlet->start_date;
  286. }
  287. return [$playletList, $playletCount];
  288. }
  289. /*
  290. * 日期范围汇总数据
  291. */
  292. public static function singlePlayletData($playletId, $startDate, $endDate, $deadLine, $minDayPaid, $maxDayPaid)
  293. {
  294. # 按条件查询小说基础数据
  295. $playletData = PlayletStatisticsData::where(function($query) use ($playletId, $startDate, $endDate, $minDayPaid, $maxDayPaid) {
  296. if($playletId) $query->where('playlet_id', $playletId);
  297. if($startDate) $query->where('start_date', '>=', $startDate);
  298. if($endDate) $query->where('start_date', '<=', $endDate);
  299. if($minDayPaid) $query->where('day_paid', '>=', $minDayPaid);
  300. if($maxDayPaid) $query->where('day_paid', '<=', $maxDayPaid);
  301. })->get();
  302. if(empty($playletData)) {
  303. return [];
  304. }
  305. $playletData = $playletData->toArray();
  306. # 循环累加 因为首日roi无法通过sum求和计算
  307. $data = [
  308. 'subscribe_date' => '',
  309. 'playlet_name' => '',
  310. 'day_paid' => 0,
  311. 'charge_total' => 0,
  312. 'fans_increase' => 0,
  313. 'new_user_charge_uv' => 0,
  314. 'new_user_charge_pv' => 0,
  315. 'first_day_roi' => 0,
  316. 'cost_cover_rate' => 0,
  317. 'new_user_cost' => 0,
  318. 'charge_user_cost' => 0,
  319. ];
  320. $realStartDate = null;
  321. $realEndDate = null;
  322. foreach($playletData as $playlet) {
  323. $item = isset($item) ? $item : ['day_charge' => 0, 'charge_total' => 0, 'day_paid' => 0];
  324. $data['subscribe_date'] = $startDate.'~'.$endDate;
  325. if(empty($realStartDate) || (!empty($realStartDate) && ($realStartDate > $playlet['start_date']))) {
  326. $realStartDate = $playlet['start_date'];
  327. }
  328. if(empty($realEndDate) || (!empty($realEndDate) && ($playlet['start_date'] > $realEndDate))) {
  329. $realEndDate = $playlet['start_date'];
  330. }
  331. $data['playlet_name'] = $playlet['playlet_name'];
  332. $data['day_paid'] = isset($data['day_paid']) ? $data['day_paid'] + $playlet['day_paid'] : 0;
  333. $data['charge_total'] = isset($data['charge_total']) ? $data['charge_total'] + $playlet['charge_total'] : 0;
  334. $data['fans_increase'] = isset($data['fans_increase']) ? $data['fans_increase'] + $playlet['fans_increase'] : 0;
  335. $data['new_user_charge_uv'] = isset($data['new_user_charge_uv']) ? $data['new_user_charge_uv'] + $playlet['new_user_charge_uv'] : 0;
  336. $data['new_user_charge_pv'] = isset($data['new_user_charge_pv']) ? $data['new_user_charge_pv'] + $playlet['new_user_charge_pv'] : 0;
  337. $data['cost_cover_rate'] = $data['day_paid'] != 0 ? ($data['charge_total'] / $data['day_paid']) : 0;
  338. $data['new_user_cost'] = $data['fans_increase'] != 0 ? ($data['day_paid'] / $data['fans_increase']) : 0;
  339. $data['charge_user_cost'] = $data['new_user_charge_uv'] != 0 ? ($data['day_paid'] / $data['new_user_charge_uv']) : 0;
  340. $chargeData = json_decode($playlet['charge_data'], 1);
  341. foreach($chargeData as $key => $charge) {
  342. if(0 == $key) {
  343. $item['day_charge'] = isset($charge['day_charge']) ? $item['day_charge'] + $charge['day_charge'] : $item['day_charge'];
  344. $item['charge_total'] = isset($charge['charge_total']) ? $item['charge_total'] + $charge['charge_total'] : $item['charge_total'];
  345. $item['day_paid'] = isset($charge['day_paid']) ? $item['day_paid'] + $charge['day_paid'] : $item['day_paid'];
  346. $data['first_day_roi'] = isset($item['day_paid']) && isset($item['day_charge']) && $item['day_paid'] != 0 ? ($item['day_charge'] / $item['day_paid']) : 0;
  347. }
  348. }
  349. }
  350. /*
  351. * 如果有收益截止日期 用截止日期来计算当日新用户累计充值以及当回本率
  352. * 然后那个(当日新用户累计充值)字段的用户订单金额 是只要(起止时间)内注册的用户充值数据
  353. * 超过(起止时间)内注册的用户不去计算
  354. */
  355. if($deadLine) {
  356. $start = $realStartDate > $startDate ? $realStartDate : $startDate;
  357. $end = $realEndDate < $endDate ? $realEndDate : $endDate;
  358. $data['charge_total'] = 0;
  359. while($start <= $end){
  360. $appIdList = AccountPromoteConfig::getAppIdsByPlayletId($playletId, $start, $start);
  361. if(!empty($appIdList)){
  362. $charge = VpOrder::getDeadLineChargeData($appIdList, $start, $start, $deadLine);
  363. $data['charge_total'] = $data['charge_total'] + (!empty($charge) ? $charge->charge_total / 10000 : 0);
  364. }
  365. $start = date('Y-m-d', strtotime($start.' +1 day'));
  366. }
  367. $data['cost_cover_rate'] = $data['day_paid'] != 0 ? ($data['charge_total'] / $data['day_paid']) : 0;
  368. }
  369. return $data;
  370. }
  371. }