566 lines
21 KiB
PHP
566 lines
21 KiB
PHP
<?php
|
||
|
||
namespace app\command;
|
||
|
||
use app\admin\controller\AdminController;
|
||
use app\common\Error;
|
||
use app\model\Admins;
|
||
use app\model\Blacks;
|
||
use app\model\FilterMobiles;
|
||
use app\model\Finances;
|
||
use app\model\Logs;
|
||
use app\model\Orders;
|
||
use app\server\Douyin;
|
||
use app\server\Meituan;
|
||
use app\server\Kuaishou;
|
||
use app\server\Orders as ServerOrders;
|
||
use app\server\SMS;
|
||
use stdClass;
|
||
use support\Log;
|
||
use support\Redis;
|
||
use Symfony\Component\Console\Command\Command;
|
||
use Symfony\Component\Console\Input\InputDefinition;
|
||
use Symfony\Component\Console\Input\InputInterface;
|
||
use Symfony\Component\Console\Input\InputOption;
|
||
use Symfony\Component\Console\Output\OutputInterface;
|
||
|
||
class SpiderDy extends Command
|
||
{
|
||
protected static $defaultName = 'spider:dy';
|
||
protected static $defaultDescription = '抖音订单拉取器';
|
||
protected $_users = [];
|
||
protected $page = 3;
|
||
|
||
protected function configure()
|
||
{
|
||
$this->setName('spider:dy')
|
||
->setDescription('抖音订单拉取器')
|
||
->setDefinition(
|
||
new InputDefinition(array(
|
||
new InputOption('sn', 'sn', InputOption::VALUE_REQUIRED),
|
||
new InputOption('os', 'os', InputOption::VALUE_REQUIRED),
|
||
new InputOption('day', 'day', InputOption::VALUE_REQUIRED),
|
||
// 最大页数
|
||
new InputOption('max_page', 'max_page', InputOption::VALUE_OPTIONAL)
|
||
))
|
||
);
|
||
}
|
||
|
||
protected function users($routeType)
|
||
{
|
||
// if (count($this->_users) > 0) return $this->_users;
|
||
$users = Admins::where('status', 1)->where('is_order', 1)->where('route_type', $routeType)->select();
|
||
$us = [];
|
||
foreach ($users as $u) {
|
||
$ru = Redis::get('CRM:USER:ONLINE:' . $u->id);
|
||
if (empty($ru)) continue;
|
||
|
||
$_u = new stdClass();
|
||
$_u->username = $u->id;
|
||
$us[] = $_u;
|
||
};
|
||
$this->_users = $us;
|
||
return $this->_users;
|
||
}
|
||
|
||
protected $_redis_pool = [];
|
||
|
||
protected function poolUser($status = 0, $categoryDesc = '')
|
||
{
|
||
$routeType = 10;
|
||
// 抖音境内外类型判断
|
||
$abroadKeyWords = ['曼谷', '港', '澳', '泰国', '普吉岛', '境外'];
|
||
foreach ($abroadKeyWords as $kw) {
|
||
if (mb_strpos($categoryDesc, $kw) !== false) {
|
||
$routeType = 20;
|
||
break;
|
||
}
|
||
}
|
||
$status .= $routeType;
|
||
if (empty($this->_redis_pool[$status])) {
|
||
$this->_redis_pool[$status] = Redis::hGetAll('CRM:Pool:' . $status);
|
||
$users = $this->users($routeType);
|
||
$_users = [];
|
||
if (empty($this->_redis_pool[$status])) {
|
||
foreach ($users as $user) {
|
||
$_users[$user->username] = 0;
|
||
Redis::hSet('CRM:Pool:' . $status, $user->username, 0);
|
||
}
|
||
$this->_redis_pool[$status] = $_users;
|
||
} else {
|
||
asort($this->_redis_pool[$status]);
|
||
$key_users = array_keys($this->_redis_pool[$status]);
|
||
$username = $key_users[0];
|
||
$max = $this->_redis_pool[$status][$username];
|
||
$_users = [];
|
||
foreach ($users as $user) {
|
||
$_users[] = $user->username;
|
||
if (!in_array($user->username, $key_users)) {
|
||
$this->_redis_pool[$status][$username] = $max;
|
||
Redis::hSet('CRM:Pool:' . $status, $user->username, $max);
|
||
}
|
||
}
|
||
foreach ($this->_redis_pool[$status] as $username => $val) {
|
||
if (!in_array($username, $_users)) {
|
||
unset($this->_redis_pool[$status][$username]);
|
||
Redis::hDel('CRM:Pool:' . $status, $username);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
$username = null;
|
||
try {
|
||
$pool = $this->_redis_pool[$status];
|
||
if (empty($pool)) $pool = [];
|
||
asort($pool);
|
||
$keys = array_keys($pool);
|
||
// if(!!config('app.debug', true)) return 1;
|
||
if (empty($keys)) {
|
||
Log::error(dirname(__FILE__) . __LINE__ . '没有可以分配的用户');
|
||
throw new \Exception('没有可以分配的用户');
|
||
}
|
||
$username = $keys[0];
|
||
$this->_redis_pool[$status][$username] += 1;
|
||
Redis::hIncrBy('CRM:Pool:' . $status, $username, 1);
|
||
} catch (\Exception $e) {
|
||
Log::error(dirname(__FILE__) . __LINE__ . '没有可以分配的用户', (array)json_encode($e));
|
||
throw new \Exception('没有可以分配的用户');
|
||
}
|
||
return $username;
|
||
}
|
||
|
||
/**
|
||
* @param InputInterface $input
|
||
* @param OutputInterface $output
|
||
* @return int
|
||
*/
|
||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||
{
|
||
// $name = $input->getArgument('name');
|
||
$output->writeln('START spider:dy:'.date('Y-m-d H:i:s'));
|
||
|
||
$os = $input->getOption('os');
|
||
$orderid = $input->getOption('sn');
|
||
$d = $input->getOption('day');
|
||
if (empty($d)) {
|
||
$d = 40;
|
||
}
|
||
if ($input->getOption('max_page')) {
|
||
$this->page = $input->getOption('max_page');
|
||
}
|
||
|
||
if ($orderid) {
|
||
$this->reloadStatus($orderid, $os, $output);
|
||
return 1;
|
||
}
|
||
|
||
// $this->checks($output);
|
||
// $output->writeln('CHECK spider:dy');
|
||
|
||
// $day = new Douyin();
|
||
// print_r($day->get(1));
|
||
// return 1;
|
||
|
||
// sleep(5);
|
||
// $time = strtotime(date('Y-m-d'));
|
||
// for ($i = 0; $i <= $d; $i++) {
|
||
// $day = $time - $i * 24 * 3600;
|
||
// $start = date('Y-m-d', $day);
|
||
// $end = date('Y-m-d 23:59:59', $day);
|
||
// $this->orders($start, $end, false);
|
||
// // $this->_kuaishouOrder($start, $end, false);
|
||
// }
|
||
|
||
// 8点20 开始分配订单
|
||
date_default_timezone_set('Asia/Shanghai');
|
||
$startTime = strtotime(date('Y-m-d 09:10'));
|
||
if (time() > $startTime) {
|
||
$start = date('Y-m-d 00:00:00', strtotime("-{$d} days"));
|
||
$end = date('Y-m-d 23:59:59');
|
||
$this->orders($start, $end, false);
|
||
}
|
||
|
||
if (date('H') >= 1 && date('H') <= 7) {
|
||
$this->reload($output);
|
||
}
|
||
|
||
$output->writeln('END spider:dy:'.date('Y-m-d H:i:s'));
|
||
return self::SUCCESS;
|
||
}
|
||
|
||
private function checks($output)
|
||
{
|
||
$i = 0;
|
||
while (true) {
|
||
$i++;
|
||
sleep(1);
|
||
$val = Redis::Rpop('Travel:Order:check:lits');
|
||
if (empty($val) || $i > 100) return;
|
||
|
||
//{\"id\":35,\"check_sn\":\"1\\u963f\\u8fbe\\u6211\\u7684as\\u7684\"}
|
||
$json = json_decode($val);
|
||
$res = (new Meituan())->voucher($json->check_sn);
|
||
|
||
//{"code":0,"message":"成功","data":{"orderId":"4944968014698378527","mobile":"18641285757","categoryId":100602,"travelDate":"2024-03-29","isConsumed":true,"categoryDesc":"出发地参团","travelBeginDate":"2024-03-29","travelEndDate":"2024-03-29"},"texInfo":null,"success":true,"fail":false}
|
||
if ($res->code === 0) { //核销成功
|
||
$order_id = $res->data->orderId;
|
||
$item = Orders::where('os', 1)->where('sn', $order_id)->find();
|
||
if (!$item) {
|
||
$this->orders($output, $order_id);
|
||
$item = Orders::where('os', 1)->where('sn', $order_id)->find();
|
||
}
|
||
|
||
if (empty($item)) {
|
||
Redis::Lpush('Travel:Order:check:lits', $val);
|
||
continue;
|
||
}
|
||
|
||
//
|
||
$checkOrder = Orders::where('check_sn', $json->check_sn)->find();
|
||
//不为空表示已经验证过了
|
||
if (!empty($checkOrder)) {
|
||
continue;
|
||
}
|
||
|
||
//查看订单是否存在
|
||
$back = false; //修改数据库成功了没有
|
||
if (!empty($json->id)) {
|
||
$order = Orders::where('id', $json->id)->find();
|
||
if ($order->sn == $order_id) {
|
||
$order->is_check = 1;
|
||
$order->check_sn = $json->check_sn;
|
||
$back = $order->save();
|
||
}
|
||
} elseif (!empty($json->admin_id)) {
|
||
//订单在自己的名下
|
||
$order = Orders::where('sn', $order_id)->where('admin_id', $json->admin_id)->find();
|
||
if (!empty($order)) {
|
||
$order->is_check = 1;
|
||
$order->check_sn = $json->check_sn;
|
||
$back = $order->save();
|
||
} else {
|
||
$order = Orders::where('sn', $order_id)->find();
|
||
// print_r($order->toArray());
|
||
if (!empty($order) && $order->admin_id == 0) {
|
||
$order->is_check = 1;
|
||
$order->check_sn = $json->check_sn;
|
||
$order->admin_id = $json->admin_id;
|
||
$back = $order->save();
|
||
} elseif (!empty($order) && $order->admin_id !== $json->admin_id) {
|
||
$item = new Orders();
|
||
$item->os = 1;
|
||
$item->sn = $order->sn;
|
||
$item->product_id = $order->product_id;
|
||
$item->product_name = $order->product_name;
|
||
$item->category_id = $order->category_id;
|
||
$item->create_at = $order->create_at;
|
||
$item->travel_date = $order->travel_date;
|
||
$item->mobile = $order->mobile;
|
||
$item->unit_price = $order->unit_price;
|
||
$item->total_price = $order->total_price;
|
||
$item->actual_price = $order->actual_price;
|
||
$item->quantity = $order->quantity;
|
||
$item->order_status = $order->order_status;
|
||
$item->refund_status = $order->refund_status;
|
||
$item->category_desc = $order->category_desc;
|
||
$item->admin_id = $json->admin_id;
|
||
$item->check_sn = $json->check_sn;
|
||
$item->is_check = 1;
|
||
$back = $item->save();
|
||
|
||
$order->is_change = $json->admin_id;
|
||
$back2 = $order->save();
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!$back) {
|
||
Redis::Lpush('Travel:Order:check:lits', $val); //写回去
|
||
Redis::Lpush('Travel:Order:check:lits:err', $val);
|
||
}
|
||
} else {
|
||
if ($res->code == 12) {
|
||
if ($json->id > 0) {
|
||
//核销码无效
|
||
$order = Orders::where('id', $json->id)->find();
|
||
$order->is_check = 2;
|
||
$order->save();
|
||
}
|
||
Logs::create([
|
||
'admin_id' => $json->admin_id ?? 0,
|
||
'action' => 11,
|
||
'order_id' => $json->id ?? 0,
|
||
'check_sn' => $json->check_sn
|
||
]);
|
||
}
|
||
if ($res->code > 0 && $res->code != 12) {
|
||
Redis::Lpush('Travel:Order:check:lits', $val);
|
||
Redis::Lpush('Travel:Order:check:lits:err', $val);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
private function orders($start = null, $end = null, $order_id = false)
|
||
{
|
||
|
||
foreach (Orders::OSS as $k => $os) {
|
||
$pages = $this->page;
|
||
$page = 1;
|
||
|
||
while (true) {
|
||
if ($page > $pages) break;
|
||
$list = [];
|
||
|
||
switch ($k) {
|
||
/*case 1:
|
||
try {
|
||
$mei = new Meituan();
|
||
$list = $mei->get($page, $start, $end);
|
||
$pages = $mei->totalPage;
|
||
} catch (\Exception $e) {
|
||
Log::error(dirname(__FILE__) . __LINE__ . $e);
|
||
}
|
||
break;*/
|
||
|
||
/*case 2:
|
||
try {
|
||
$kuai = new Kuaishou();
|
||
$list = $kuai->get($page, $start, $end);
|
||
$pages = $kuai->totalPage;
|
||
} catch (\Exception $e) {
|
||
Log::error(dirname(__FILE__) . __LINE__ . $e);
|
||
}
|
||
break;*/
|
||
|
||
case 3:
|
||
case 5:
|
||
try {
|
||
$dou = new Douyin($k);
|
||
$list = $dou->get($page, $start, $end, '');
|
||
$pages = $dou->totalPage;
|
||
} catch (\Exception $e) {
|
||
Log::error(dirname(__FILE__) . __LINE__ . $e);
|
||
}
|
||
break;
|
||
|
||
default:
|
||
# code...
|
||
break;
|
||
}
|
||
|
||
foreach ($list as $order) {
|
||
// 过滤一日游
|
||
if ($order->os == 3 && strpos($order->category_desc, '一日游') !== false) {
|
||
Log::info("抖音 跳过订单:{$order->order_id}");
|
||
continue;
|
||
}
|
||
|
||
$item = Orders::where('os', $order->os)->where('sn', $order->sn)->find();
|
||
|
||
if (empty($item)) {
|
||
$order->is_zhibo = $this->zhibo($order->product_name, $order->os);
|
||
|
||
if (FilterMobiles::isFilterMobile($order->mobile)) {
|
||
$admin_id = 0;
|
||
} else {
|
||
$oldMobile = orders::where("os", $order->os)->where("mobile", $order->mobile)
|
||
->where("create_time", '>=', time() - 24 * 3600 * 3)->find();
|
||
|
||
if (!empty($oldMobile)) {
|
||
$admin_id = $oldMobile->admin_id;
|
||
} else {
|
||
// $admin_id = $this->poolUser($order->orderStatus, $order->category_desc);
|
||
try {
|
||
$admin_id = \app\server\Orders::poolUser($order->orderStatus, $order->product_id);
|
||
} catch (\Exception $exception) {
|
||
Log::info(sprintf('dy create order fail:%s, order_id:%s', $exception->getMessage(), $order->sn));
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (empty($admin_id)) return null;
|
||
}
|
||
|
||
$order->admin_id = $admin_id;
|
||
$order->give_time = time();
|
||
//判断是否需要发短信
|
||
if ($order->order_status == 1) {
|
||
$this->sms($admin_id, $order);
|
||
}
|
||
|
||
$work = \app\server\Orders::getLiveRoomWork($order->create_at, $order->product_id);
|
||
if ($work) {
|
||
$order->live_room_work_id = $work->id;
|
||
}
|
||
|
||
//新获得一个用户的,提示管理员有新的订单
|
||
Redis::incrBy('CRM:USER:ONLINE:NEW:' . $admin_id, 1);
|
||
$item = new Orders();
|
||
}
|
||
|
||
if ($order->travel_date && empty($order->travel_end)) {
|
||
$days = $this->_days($order);
|
||
if ($days) {
|
||
$order->travel_end = date('Y-m-d 00:00:00', strtotime($order->travel_date) + $days * 24 * 3600);
|
||
}
|
||
}
|
||
|
||
if ($item->order_status !== 2 && $order->order_status == 2) {
|
||
Redis::incrBy('CRM:USER:WRITE:OFF:' . $item->admin_id, 1);
|
||
}
|
||
|
||
$back = $item->save($order->toArray());
|
||
|
||
if ($back) {
|
||
$this->_finance(0, $item->id, $item->asset_price);
|
||
}
|
||
}
|
||
|
||
// 数据小于50, 结束
|
||
if (empty($list)) {
|
||
break;
|
||
}
|
||
$page++;
|
||
}
|
||
}
|
||
}
|
||
|
||
private function reload($output)
|
||
{
|
||
$back = Redis::set('SpiderMt:reload:dy:lock', time(), 'EX', 3600 * 8, 'NX');
|
||
if (!$back) return;
|
||
$orders = Orders::where('create_at', '<=', (time() - 15 * 24 * 3600) * 1000)->whereIn('os', [3, 5])->wherein('status', [1, 2, 3])->select();
|
||
foreach ($orders as $order) {
|
||
$this->reloadStatus($order->sn, $order->os, $output);
|
||
}
|
||
}
|
||
|
||
private function reloadStatus($orderid, $os, $output)
|
||
{
|
||
$w[] = ['sn', '=', $orderid];
|
||
if ($os) $w[] = ['os', '=', $os];
|
||
$item = Orders::where($w)->find();
|
||
if (empty($item)) {
|
||
$output->writeln('没有找到订单');
|
||
}
|
||
$it = null;
|
||
switch ($item->os) {
|
||
/*case 1:
|
||
$m = new Meituan();
|
||
$it = $m->get(1, null, null, $item->sn);
|
||
break;
|
||
case 2:
|
||
$m = new Kuaishou();
|
||
$it = $m->get(1, null, null, $item->sn);
|
||
break;*/
|
||
case 3:
|
||
case 5:
|
||
$m = new Douyin($item->os);
|
||
$it = $m->get(1, null, null, $item->sn);
|
||
break;
|
||
|
||
default:
|
||
# code...
|
||
break;
|
||
}
|
||
|
||
if ($it) {
|
||
$back = $item->save($it[0]);
|
||
if ($back) {
|
||
$this->_finance(0, $item->id, $item->asset_price);
|
||
}
|
||
} else {
|
||
$output->writeln('没有拉取到数据:' . $orderid);
|
||
}
|
||
|
||
}
|
||
|
||
private function _days($order)
|
||
{
|
||
if (stripos($order->product_name, '一日') !== false) {
|
||
return 1;
|
||
}
|
||
preg_match('/(\d)天/', $order->product_name, $all);
|
||
if (!empty($all) && intval($all[1]) > 0) {
|
||
return $all[1];
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
private function sms($admin_id, $order)
|
||
{
|
||
$user = Admins::cache(true)->where('id', $admin_id)->find();
|
||
if ((!config('app.debug', true) || config('app.debug', true) === 'false') && (time() * 1000 - $order->create_at) / 1000 < 2 * 24 * 3600) {
|
||
$has = Blacks::where('mobile', $order->mobile)->find();
|
||
if (empty($has) && !empty($order->mobile)) {
|
||
SMS::juhe_sms_send($order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]);
|
||
} else {
|
||
sleep(10);
|
||
}
|
||
|
||
} else {
|
||
print_r([$order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]]);
|
||
}
|
||
}
|
||
|
||
private function zhibo($title, $os = 0)
|
||
{
|
||
if ($os == 3) {
|
||
|
||
return 2;
|
||
}
|
||
if (
|
||
strlen($title) - 3 == strripos($title, "!")
|
||
|| strlen($title) - 1 == strripos($title, "!")
|
||
) {
|
||
return 1;
|
||
} elseif (strlen($title) - 3 == strripos($title, "甄")) {
|
||
return 2;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
private function _finance($type = 1, $order_id = 0, $price = 0)
|
||
{
|
||
// $fa = Finances::where('order_id', $order_id)->where('type', $type)->find();
|
||
$back = Redis::set('CRM:ORDER:LOCK:' . $order_id, time(), 'EX', 6, 'NX');
|
||
if (!$back) return;
|
||
|
||
$total = Finances::where('order_id', $order_id)->sum('total'); //总的关于这个订单的金额
|
||
//如果总金额大于提交上来的核销金额,那就是退费的
|
||
//如果提交上来的金额小于总金额,那就是核销的
|
||
if ($total > $price) {
|
||
$type = 2;
|
||
$fee = -($total - $price);
|
||
} elseif ($total < $price) {
|
||
$type = 1;
|
||
$fee = $price - $total;
|
||
} else {
|
||
return;
|
||
}
|
||
|
||
Finances::create([
|
||
'order_id' => $order_id,
|
||
'type' => $type,
|
||
'total' => $fee,
|
||
'status' => 1
|
||
]);
|
||
return;
|
||
|
||
// if(empty($fa)) {
|
||
// if($type == 2) {
|
||
// $has = Finances::where('order_id', $order_id)->where('type', 1)->find();
|
||
// if(empty($has)) return;
|
||
// }
|
||
// Finances::create([
|
||
// 'order_id' => $order_id,
|
||
// 'type' => $type,
|
||
// 'total' => $price,
|
||
// 'status' => 1
|
||
// ]);
|
||
// }
|
||
}
|
||
}
|