setName('settlement')->setDescription('后台统计用户流量使用, 并扣除相关流量'); } /** * 功能: 执行任务, 计算统计使用的流量 * @param Input $input * @param Output $output * @return int|void|null * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ protected function execute(Input $input, Output $output) { // parent::execute($input, $output); // TODO: Change the autogenerated stub // $output->writeln("TestCommand"); $dateTime = time() - 5; // 设置处理数据的时间为当前时间的前5秒,避免数据竞争 // 分组统计出指定时间内用户使用了的流量 $data = FlowLogs::where('created_at', '<=', $dateTime) ->field('username, sum(bytes) as bytes') ->group('username') ->select()->toArray(); // 当前数据所有用户名的数组 $userNames = array_column($data, 'username'); $users = collection(User::whereIn('username', $userNames)->field('id, username')->select())->toArray(); $userNameMapId = array_column($users, 'id', 'username'); Db::startTrans(); try { foreach ($data as $datum) { if (!$this->usedTraffic($userNameMapId[$datum['username']], $datum['bytes'], $dateTime)) { throw new Exception('统计用户使用流量失败'); } } // 指定时间段的流量已统计,删除相关记录 FlowLogs::where('created_at', '<=', $dateTime)->delete(); // 统计数据成功,提交事物 Db::commit(); } catch (Exception $e) { // 操作失败,数据库信息回滚 Db::rollback(); } } /** * 功能: 处理用户使用过的流量 * @param $uid * @param $bytes * @param $dateTime * @return bool * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\ModelNotFoundException * @throws \think\exception\DbException */ protected function usedTraffic($uid, $bytes, $dateTime) { $usable = UserTraffic::where('uid', $uid) ->where('is_usable', '1') ->where('start_time', '<', $dateTime) ->where('expired_time', '>', $dateTime) ->order('expired_time', 'ASC') ->select(); if (empty($usable)) { return true; } foreach ($usable as $item) { if ($bytes == 0) break; if ($item->traffic > bcadd($item->used, $bytes)) { $item->setInc('used', $bytes); } else { $bytes = bcsub($bytes, bcsub($item->traffic, $item->used)); $item->used = $item->traffic; $item->is_usable = 0; $item->save(); } } return true; } }