小说推广数据系统

NovelDataTotalStatistics.php 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <?php
  2. /**
  3. * Created by PhpStorm.
  4. * User: shensong
  5. * Date: 2021/6/3
  6. * Time: 18:17
  7. */
  8. namespace App\Console\Commands;
  9. use App\Models\Account;
  10. use App\Models\AdsDailyReport;
  11. use App\Models\AdsReport;
  12. use App\Models\conf\AccountConf;
  13. use App\Models\conf\ADQAccountBinding;
  14. use App\Models\conf\NovelConf;
  15. use App\Models\NovelDataTotal;
  16. use App\Models\Order;
  17. use App\Services\WxAccountService;
  18. use Illuminate\Console\Command;
  19. class NovelDataTotalStatistics extends Command
  20. {
  21. protected $signature = 'NovelDataTotalStatistics';
  22. protected $description = '小说数据趋势每日离线统计';
  23. public function handle($date = null)
  24. {
  25. self::infoMessage(' 开始执行;');
  26. \DB::connection()->disableQueryLog();
  27. set_time_limit(0);
  28. ini_set("memory_limit",-1);
  29. $yesterday = date('Y-m-d', strtotime('-1 day'));
  30. try {
  31. //从关系表中聚合查询小说id列表
  32. $novelAccountList = AccountConf::query()
  33. ->where('enable', 1)
  34. // ->where('novel_id', 108)
  35. ->get()
  36. ->toArray();
  37. $novelIdList = array_unique(array_column($novelAccountList, 'novel_id'));
  38. //查询小说配置表
  39. $novelList = NovelConf::query()
  40. ->select('id', 'name')
  41. ->where('enable', 1)
  42. ->whereIn('id', $novelIdList)
  43. ->get()
  44. ->keyBy('id')
  45. ->toArray();
  46. self::infoMessage(' 获取小说数据完成:共' . count($novelList) . '条小说数据');
  47. // 删除昨日离线数据,只保留当天的
  48. NovelDataTotal::query()
  49. ->where('created_date', $yesterday)
  50. ->delete();
  51. //遍历小说列表(内部循环处理小说对应的公众号数据)
  52. /*将所有需要累加的数据据都汇总完成*/
  53. foreach ($novelIdList as $novelId) {
  54. # 查询小说基础信息为空,说明小说已经删除,直接跳过不处理
  55. $novelData = isset($novelList[$novelId]) ? $novelList[$novelId] : null;
  56. if(is_null($novelData)) {
  57. self::infoMessage(' 小说基础数据查询为空#novel_id:'.$novelId);
  58. continue;
  59. }
  60. self::infoMessage(' 开始处理小说:'.$novelData['name'].'的数据');
  61. #查询小说对应的公众号配置信息
  62. $accountConfList = AccountConf::query()
  63. ->where('novel_id', $novelId)
  64. ->where('enable', 1)
  65. ->get();
  66. if(empty($accountConfList)) {
  67. self::infoMessage(' 小说对应公众号配置信息为空');
  68. continue;
  69. }
  70. $accountConfList = $accountConfList->toArray();
  71. # 数据初始化
  72. $novel['start_date'] = null;
  73. $novel['end_date'] = null;
  74. $novel['id'] = $novelId;
  75. $novel['name'] = $novelData['name'];
  76. $novel['first_day_roi'] = 0;
  77. $novel['new_user_cost'] = 0;
  78. $novel['new_user_charge'] = 0;
  79. $novel['day_paid'] = 0;
  80. $novel['charge_total'] = 0;
  81. $novel['cost_cover_rate'] = 0;
  82. $novel['fans_increase'] = 0;
  83. $novel['new_user_charge_uv'] = 0;
  84. $novel['new_user_charge_pv'] = 0;
  85. $novel['charge_data'] = '';
  86. $novel['charge_user_cost'] = 0;
  87. $chargeData = [];
  88. # 循环小说公众号配置
  89. foreach ($accountConfList as $conf) {
  90. # 给小说的起始截止日期赋予默认值
  91. if (is_null($novel['start_date']) || ($conf['start_date'] < $novel['start_date'])) {
  92. $novel['start_date'] = $conf['start_date'];
  93. }
  94. if (is_null($novel['end_date']) || ($conf['end_date'] > $novel['end_date'])) {
  95. $novel['end_date'] = $conf['end_date'];
  96. }
  97. # 直接从表中查询消耗总数(避免出现由于当天无充值信息丢失消耗数据的情况)
  98. $adsReport = AdsReport::query()
  99. ->selectRaw('sum(paid) as paid, sum(follow_uv) as follow_uv')
  100. ->where('app_id', $conf['app_id'])
  101. ->where('ref_date', '>=', $conf['start_date'])
  102. ->where('ref_date', '<=', $conf['end_date'])
  103. ->first();
  104. # 当天消耗
  105. $novel['day_paid'] = isset($adsReport->paid) ? $novel['day_paid'] + $adsReport->paid / 100 : $novel['day_paid'];
  106. # 新增粉丝数
  107. $novel['fans_increase'] = isset($adsReport->follow_uv) ? $novel['fans_increase'] + $adsReport->follow_uv : $novel['fans_increase'];
  108. $adqAdsReport = self::getAdqPaid($conf['app_id'], $conf['start_date'], $conf['end_date']);
  109. $novel['day_paid'] = isset($adqAdsReport['day_paid']) ? $novel['day_paid'] + $adqAdsReport['day_paid'] : $novel['day_paid'];
  110. $novel['fans_increase'] = isset($adqAdsReport['fans_increase']) ? $novel['fans_increase'] + $adqAdsReport['fans_increase'] : $novel['fans_increase'];
  111. # 从趋势列表中获取数据
  112. $data = self::paymentTrend($conf['app_id'], $conf['start_date'], $conf['end_date']);
  113. if (empty($data)) {
  114. self::infoMessage(' 小说:' . $novel['name'] . '在' . $conf['start_date'] . '日的趋势数据为空');
  115. $data = [];
  116. } else {
  117. $data = json_decode(json_encode($data), 1);
  118. }
  119. # 这部分主要是用来循环累加数据
  120. foreach ($data as $item) {
  121. # 当日新用户累计充值
  122. $novel['charge_total'] = isset($item['charge_total']) ? $novel['charge_total'] + $item['charge_total'] : $novel['charge_total'];
  123. # 充值人数
  124. $novel['new_user_charge_uv'] = isset($item['new_user_charge_uv']) ? $novel['new_user_charge_uv'] + $item['new_user_charge_uv'] : $novel['new_user_charge_uv'];
  125. # 充值次数
  126. $novel['new_user_charge_pv'] = isset($item['new_user_charge_pv']) ? $novel['new_user_charge_pv'] + $item['new_user_charge_pv'] : $novel['new_user_charge_pv'];
  127. # 开始日志至之后的100天数据
  128. for ($i = 0; $i < WxAccountService::DAYS; $i++) {
  129. if (isset($chargeData[$i])) {
  130. //已存在,累加
  131. $chargeData[$i]['day_charge'] = isset($item['chargeData'][$i]['day_charge']) ? $chargeData[$i]['day_charge'] + $item['chargeData'][$i]['day_charge'] : $chargeData[$i]['day_charge'];
  132. $chargeData[$i]['charge_total'] = isset($item['chargeData'][$i]['charge_total']) ? $chargeData[$i]['charge_total'] + $item['chargeData'][$i]['charge_total'] : $chargeData[$i]['charge_total'];
  133. $chargeData[$i]['day_paid'] = isset($item['chargeData'][$i]['day_paid']) ? $chargeData[$i]['day_paid'] + $item['chargeData'][$i]['day_paid'] : $chargeData[$i]['day_paid'];
  134. } else {
  135. //不存在,赋值
  136. $chargeData[$i]['day_charge'] = isset($item['chargeData'][$i]['day_charge']) ? $item['chargeData'][$i]['day_charge'] : 0;
  137. $chargeData[$i]['charge_total'] = isset($item['chargeData'][$i]['charge_total']) ? $item['chargeData'][$i]['charge_total'] : 0;
  138. $chargeData[$i]['day_paid'] = isset($item['chargeData'][$i]['day_paid']) ? $item['chargeData'][$i]['day_paid'] : 0;
  139. }
  140. }
  141. }
  142. }
  143. /*对累加完成的数据进行计算*/
  144. # 首日ROI
  145. $novel['first_day_roi'] = isset($chargeData[0]['day_paid']) && isset($chargeData[0]['day_charge']) && $chargeData[0]['day_paid'] != 0 ? ($chargeData[0]['day_charge'] / $chargeData[0]['day_paid']) : 0;
  146. # 回本率
  147. $novel['cost_cover_rate'] = $novel['day_paid'] != 0 ? ($novel['charge_total'] / $novel['day_paid']) : 0;
  148. # 新增粉丝成本
  149. $novel['new_user_cost'] = $novel['fans_increase'] != 0 ? ($novel['day_paid'] / $novel['fans_increase']) : 0;
  150. # 充值用户成本
  151. $novel['charge_user_cost'] = $novel['new_user_charge_uv'] != 0 ? ($novel['day_paid'] / $novel['new_user_charge_uv']) : 0;
  152. for ($i = 0; $i < WxAccountService::DAYS; $i++) {
  153. $chargeData[$i]['day_charge'] = isset($chargeData[$i]['day_charge']) ? $chargeData[$i]['day_charge'] : 0;
  154. $chargeData[$i]['charge_total'] = isset($chargeData[$i]['charge_total']) ? $chargeData[$i]['charge_total'] : 0;
  155. $chargeData[$i]['day_paid'] = isset($chargeData[$i]['day_paid']) ? $chargeData[$i]['day_paid'] : 0;
  156. $chargeData[$i]['day_add'] = (isset($chargeData[$i]['day_paid']) && $chargeData[$i]['day_paid'] != 0) ? ($chargeData[$i]['day_charge'] / $chargeData[$i]['day_paid']) : 0;
  157. $chargeData[$i]['day_cover'] = (isset($chargeData[$i]['day_paid']) && $chargeData[$i]['day_paid'] != 0) ? ($chargeData[$i]['charge_total'] / $chargeData[$i]['day_paid']) : 0;
  158. $chargeData[$i]['day_times'] = isset($chargeData[$i]['day_paid']) && ($chargeData[$i]['day_paid'] * $novel['first_day_roi']) != 0 ? ($chargeData[$i]['charge_total'] / ($chargeData[$i]['day_paid'] * $novel['first_day_roi'])) : 0;
  159. }
  160. $novel['charge_data'] = json_encode($chargeData);
  161. self::insertData($novel);
  162. unset($novel);
  163. unset($chargeData);
  164. unset($data);
  165. }
  166. # 汇总数据统计
  167. $this->infoMessage(' 数据整理完成');
  168. } catch (\Exception $exception) {
  169. $this->infoMessage(' 异常###file:'.$exception->getFile().';line:'.$exception->getLine().';message:'.$exception->getMessage());
  170. }
  171. }
  172. /*
  173. * 付费趋势
  174. * */
  175. public static function paymentTrend($appId, $startDate, $endDate)
  176. {
  177. # 获取对应的公众号AppId
  178. if(is_array($appId)){
  179. $appIdList = $appId;
  180. } else {
  181. $appIdList = [$appId];
  182. }
  183. $dateList = [];
  184. $date = $startDate;
  185. while($date <= $endDate) {
  186. $dateList[] = $date;
  187. $date = date('Y-m-d', strtotime($date . ' +1 day'));
  188. }
  189. if(empty($dateList)) return [[], 0];
  190. # 查询账号信息
  191. $accountData = Account::query()->selectRaw("channel_id, platform_id, nickname, app_id")
  192. ->whereIn('app_id', $appIdList)->get();
  193. # 获取各日期对应的花费
  194. $paidList = AdsReport::getPaidData($appIdList, $dateList);
  195. $adqAccountList = ADQAccountBinding::getAdqAccountList($appId, $startDate, $endDate);
  196. $adqAccounts = $adqDateList = [];
  197. foreach($adqAccountList as $item) {
  198. $adqAccounts = array_merge($adqAccounts, $item);
  199. }
  200. $adqAccounts = array_unique($adqAccounts);
  201. $adqDateList = array_keys($adqAccountList);
  202. if(!empty($adqAccounts)) {
  203. $adqPaidList = AdsReport::getAdqPaidData($adqAccounts, $adqDateList);
  204. } else {
  205. $adqPaidList = null;
  206. }
  207. $channelIdList = $accountData->where('app_id', $appId)->pluck('channel_id')->toArray();
  208. $fansDataList = self::subscribedFansData($channelIdList, $startDate, $endDate);
  209. $result = [];
  210. foreach($dateList as $value) {
  211. $trend = [];
  212. $fansData = $fansDataList->where('subscribed_date', $value)->first();
  213. $fansData = json_decode(json_encode($fansData), 1);
  214. $trend['fans_total'] = !empty($fansData) ? $fansData['fans_total'] : 0;
  215. $trend['charge_total'] = !empty($fansData) ? $fansData['charge_total'] / 100 : 0;
  216. $trend['app_id'] = $appId;
  217. # 当日消耗
  218. $paidInfo = $paidList->where('app_id', $appId)
  219. ->where('ref_date', $value)
  220. ->first();
  221. $trend['day_paid'] = isset($paidInfo->paid) ? ($paidInfo->paid / 100) : 0;
  222. $dailyAdqAccount = isset($adqAccountList[$value]) ? $adqAccountList[$value] : [];
  223. $dailyAdqPaid = !empty($dailyAdqAccount) && $adqPaidList->isNotEmpty() ?
  224. $adqPaidList->whereIn('account_id', $dailyAdqAccount)->where('ref_date', $value)->sum('paid') : 0;
  225. $trend['day_paid'] += $dailyAdqPaid/100;
  226. # 获取当日新用户充值情况
  227. $newUserCharge = Order::getNewUserChargeSecond($value, $channelIdList);
  228. $trend['new_user_charge'] = isset($newUserCharge->new_user_charge) ? $newUserCharge->new_user_charge : 0;
  229. $trend['new_user_charge_uv'] = isset($newUserCharge->new_user_charge_uv) ? $newUserCharge->new_user_charge_uv : 0;
  230. $trend['new_user_charge_pv'] = isset($newUserCharge->new_user_charge_pv) ? $newUserCharge->new_user_charge_pv : 0;
  231. $trend['first_day_roi'] = $trend['day_paid'] != 0 ? (($trend['new_user_charge'] / 100) / $trend['day_paid']) : 0;
  232. // 充增回倍
  233. $trend['chargeData'] = WxAccountService::chargeAfterSubscribed(
  234. $channelIdList, $value, WxAccountService::DAYS, $appId, $trend['first_day_roi'], $trend['day_paid']
  235. );
  236. $result[] = $trend;
  237. }
  238. return $result;
  239. }
  240. public static function insertData($novel)
  241. {
  242. $insertData['start_date'] = $novel['start_date'];
  243. $insertData['end_date'] = $novel['end_date'];
  244. $insertData['novel_name'] = $novel['name'];
  245. $insertData['novel_id'] = $novel['id'];
  246. $insertData['first_day_roi'] = $novel['first_day_roi'];
  247. $insertData['day_paid'] = $novel['day_paid'];
  248. $insertData['charge_total'] = $novel['charge_total'];
  249. $insertData['cost_cover_rate'] = $novel['cost_cover_rate'];
  250. $insertData['fans_increase'] = $novel['fans_increase'];
  251. $insertData['new_user_cost'] = $novel['new_user_cost'];
  252. $insertData['new_user_charge_uv'] = $novel['new_user_charge_uv'];
  253. $insertData['new_user_charge_pv'] = $novel['new_user_charge_pv'];
  254. $insertData['charge_user_cost'] = $novel['charge_user_cost'];
  255. $insertData['charge_data'] = $novel['charge_data'];
  256. $insertData['created_date'] = date('Y-m-d');
  257. $row = NovelDataTotal::query()
  258. ->where('novel_id', $insertData['novel_id'])
  259. ->where('created_date', $insertData['created_date'])
  260. ->first();
  261. if($row) {
  262. # 更新
  263. $res = NovelDataTotal::query()
  264. ->where('novel_id', $insertData['novel_id'])
  265. ->where('created_date', $insertData['created_date'])
  266. ->update($insertData);
  267. if($res) {
  268. # 更新成功
  269. self::infoMessage(' 处理小说:'.$insertData['novel_name'].'的数据;处理结果:更新成功');
  270. } else {
  271. # 未有修改
  272. self::infoMessage(' 处理小说:'.$insertData['novel_name'].'的数据;处理结果:更新失败');
  273. }
  274. } else {
  275. # 插入
  276. $res = NovelDataTotal::query()->insert($insertData);
  277. if($res) {
  278. # 新增成功
  279. self::infoMessage(' 处理小说:'.$insertData['novel_name'].'的数据;处理结果:新增成功');
  280. } else {
  281. # 新增失败
  282. self::infoMessage(' 处理小说:'.$insertData['novel_name'].'的数据;处理结果:新增失败');
  283. }
  284. }
  285. }
  286. public static function infoMessage($message)
  287. {
  288. echo date('Y-m-d H:i:s') . $message .' 内存占用:'.round(memory_get_usage()/1024/1024, 2).'MB'. "\r\n";
  289. }
  290. public static function getAdqPaid($appId, $startDate, $endDate)
  291. {
  292. # 查询所有配置
  293. $confList = ADQAccountBinding::query()
  294. ->where('enable', 1)
  295. ->where('status', 1)
  296. ->where('app_id', $appId)
  297. ->where('start_date', '<=', $endDate)
  298. ->where('end_date', '>=', $startDate)
  299. ->get();
  300. $data = ['day_paid' => 0, 'fan_increase' => 0];
  301. if($confList->isNotEmpty()) {
  302. foreach ($confList->toArray() as $conf) {
  303. $start = $conf['start_date'] >= $startDate ? $conf['start_date'] : $startDate;
  304. $end = $conf['end_date'] <= $endDate ? $conf['end_date'] : $endDate;
  305. $paidInfo = AdsDailyReport::query()
  306. ->where('account_id', $conf['account_id'])
  307. ->where('ref_date', '>=', $start)
  308. ->where('ref_date', '<=', $end)
  309. ->selectRaw('sum(paid) as paid, sum(follow_uv) as follow_uv')
  310. ->first();
  311. $data['day_paid'] += $paidInfo->paid/100;
  312. $data['fan_increase'] += $paidInfo->follow_uv;
  313. }
  314. }
  315. return $data;
  316. }
  317. /*
  318. * 获取时间段内新关注粉丝数据
  319. * */
  320. public static function subscribedFansData($channelIds, $startDate, $endDate)
  321. {
  322. # 获取公众号数据
  323. $list = Order::query()
  324. ->selectRaw("DATE_FORMAT(user_created_at, '%Y-%m-%d') as subscribed_date,
  325. count(member_openid) as fans_total, channel_id, sum(price) as charge_total")
  326. ->where('enable', 1)
  327. ->where(function($query) use($channelIds) {
  328. if(!empty($channelIds))
  329. $query->whereIn('channel_id', $channelIds);
  330. })
  331. ->where('user_created_at', '>=', $startDate . ' 00:00:00')
  332. ->where('user_created_at', '<=', $endDate . ' 23:59:59')
  333. ->where('status', 1)
  334. ->groupBy(['subscribed_date'])
  335. ->get();
  336. return $list;
  337. }
  338. }