vd/app/Domains/Virtual/Services/FlowPoolService.php
2019-04-15 17:13:33 +08:00

716 lines
26 KiB
PHP

<?php
namespace App\Domains\Virtual\Services;
use App\Dicts;
use Carbon\Carbon;
use App\Core\Service;
use Illuminate\Validation\Rule;
use App\Models\Virtual\FlowPool;
use Illuminate\Support\Facades\DB;
use App\Models\Virtual\FlowPoolData;
use App\Exceptions\NotExistException;
use App\Models\Virtual\FlowPoolMonth;
use Illuminate\Support\Facades\Schema;
use App\Exceptions\NotAllowedException;
use App\Models\Virtual\FlowPoolSetting;
use Illuminate\Support\Facades\Validator;
use App\Models\Virtual\OrderCardPartition;
use App\Exceptions\InvalidArgumentException;
use Illuminate\Pagination\LengthAwarePaginator;
use App\Domains\Virtual\Services\CompanyService;
use App\Domains\Virtual\Services\PackageService;
use App\Domains\Virtual\Repositories\FlowPoolRepository;
use App\Domains\Virtual\Repositories\FlowPoolSettingRepository;
use App\Domains\Virtual\Repositories\OrderCardPartitionRepository;
use App\Domains\Real\Repositories\FlowPoolRepository as RealFlowPoolRepository;
class FlowPoolService extends Service
{
protected $realFlowPoolRepository;
protected $flowPoolRepository;
protected $flowPoolPackageRepository;
protected $flowPoolSettingRepository;
protected static $pools;
/**
* 构造函数
*
* @return void
*/
public function __construct(
RealFlowPoolRepository $realFlowPoolRepository,
FlowPoolRepository $flowPoolRepository,
FlowPoolSettingRepository $flowPoolSettingRepository
) {
$this->realFlowPoolRepository = $realFlowPoolRepository;
$this->flowPoolRepository = $flowPoolRepository;
$this->flowPoolSettingRepository = $flowPoolSettingRepository;
}
/**
* RD流量池列表
*
* @return void
*/
public function real()
{
$used = $this->flowPoolRepository->select(['id', 'real_pool_ids'])->get();
$array = [];
foreach ($used as $item) {
$item['real_pool_ids'] = $item['real_pool_ids'] ?? [];
foreach ($item['real_pool_ids'] as $key => $value) {
$array[$value] = $item['id'];
}
}
$list = $this->realFlowPoolRepository->get();
$list->map(function ($item) use ($array) {
$item->virtual_pool_id = $array[$item['id']] ?? 0;
});
return $list;
}
/**
* 定价列表
*
* @return void
*/
public function packages(array $conditions = [])
{
$used = $this->flowPoolRepository->select(['id', 'company_id', 'package_ids'])->get();
$array = [];
foreach ($used as $item) {
$item['package_ids'] = $item['package_ids'] ?? [];
foreach ($item['package_ids'] as $key => $value) {
if (isset($array[$value])) {
array_push($array[$value], $item['company_id']);
} else {
$array[$value] =[$item['company_id']];
}
}
}
$packages = app(PackageService::class)->index(['limit' => 0]);
$packages = $packages->where('status', 0)->where('flowed', 1);
$packages->map(function ($item) use ($array) {
$item->company_ids = $array[$item['id']] ?? [];
});
return $packages->values();
}
/**
* 流量池列表
*
* @param array $conditions
* @return mixed
*/
public function index(array $conditions = [])
{
$limit = $conditions['limit'] ?? 20;
$month = Carbon::parse($conditions['month']);
$flowPools = $this->flowPoolRepository->withConditions($conditions)
->applyConditions()->paginate($limit);
$flowPools = $this->transformer($flowPools, $month);
$flows = DB::select('select * from get_flow_pool_month_stat(?)', [$month->format('Ym')]);
$flows = collect($flows)->collect()->keyBy('pool_id')->toArray();
// 流量统计
$flowPools->map(function ($flowPool) use ($flows) {
$flowArry = $flows[$flowPool->id];
// 无使用流量
if (!$flowArry) {
$flowPool->members = 0;
$flowPool->minimum_flows = $this->humanFlows(0);
$flowPool->excess_flows = $this->humanFlows(0);
$flowPool->minimum_price = sprintf('%.02f', 0);
$flowPool->excess_price = sprintf('%.02f', 0);
$flowPool->total_price = sprintf('%.02f', 0);
return $flowPool;
}
$flowPool->members = $flowArry['members'];
$flowPool->minimum_flows = $this->humanFlows($flowArry['minimum_flows']);
$flowPool->excess_flows = $this->humanFlows($flowArry['excess_flows']);
$flowPool->minimum_price = sprintf('%.02f', $flowArry['minimum_price'] / 100);
$flowPool->excess_price = sprintf('%.02f', $flowArry['excess_price'] / 100);
$flowPool->total_price = sprintf('%.02f', $flowPool->minimum_price + $flowPool->excess_price);
return $flowPool;
});
$flowPools->map(function ($item) {
$item = $this->transformerSettings($item);
});
return $flowPools;
}
/**
* 存储流量池
*
* @param array $attributes
* @return FlowPool
*/
public function store(array $attributes = [])
{
$attributes['start_at'] = $attributes['start_at'] ? Carbon::parse($attributes['start_at'])->startOfMonth()->format('Y-m-d H:i:s') : Carbon::now()->startOfMonth()->format('Y-m-d H:i:s');
$attributes['end_at'] = $attributes['status'] ? Carbon::now()->endOfMonth()->format('Y-m-d H:i:s') : null;
$attributes = array_only($attributes, ['id', 'company_id', 'name', 'flows', 'carrier_operator', 'shared', 'package_ids', 'real_pool_ids', 'remark', 'start_at', 'end_at']);
$rule = [
'name' => ['required', 'between:2,32', Rule::unique($this->flowPoolRepository->getTable(), 'name')->ignore($attributes['id'])->whereNUll('deleted_at')],
'carrier_operator' => ['required', 'in:0,1,2,3'],
'shared' => ['in:1,2'],
];
$message = [
'name.required' => '请输入流量池名称',
'name.between' => '流量池名称长度不合法',
'name.unique' => '流量池名称已经被其他用户所使用',
'carrier_operator.required' => '请选择运营商',
'carrier_operator.in' => '运营商不合法',
// 'shared.required' => '请选择共享类型',
'shared.in' => '共享类型不合法',
];
Validator::validate($attributes, $rule, $message);
if (!$attributes['id']) {
$maxId = FlowPool::withTrashed()->max('id');
$attributes['id'] = $maxId ? $maxId + 1 : 1;
$attributes['sn'] = self::sn($attributes['id']);
$node = $this->flowPoolRepository->create($attributes);
}
if ($attributes['id']) {
$node = $this->flowPoolRepository->find($attributes['id']);
if (!$node) {
throw new NotExistException('流量池不存在或已删除');
}
$this->flowPoolRepository->setModel($node)->update($attributes);
}
return $node;
}
/**
* 删除
*
* @return bool
*/
public function destroy($ids)
{
$ids = is_array($ids) ? $ids : [$ids];
$this->flowPoolRepository->destroy($ids);
return true;
}
/**
* 计费规则
*
* @param array $attributes
* @return FlowPool
*/
public function setting(array $attributes = [])
{
$attributes['start_at'] = Carbon::parse($attributes['start_at'])->startOfMonth()->format('Y-m-d H:i:s');
$attributes['end_at'] = Carbon::parse($attributes['end_at'])->endOfMonth()->format('Y-m-d H:i:s');
$attributes['gradient_price'] = intval($attributes['gradient_price'] * 100);
$attributes['gradient'] = floatval($attributes['gradient']);
if (!is_array($attributes['minimum_settings'])) {
throw new InvalidArgumentException('保底流量设置必须为数组');
}
$minimum_settings = $attributes['minimum_settings'];
foreach ($minimum_settings as &$item) {
$item = array_only($item, ['package_id', 'price', 'flows']);
$item['price'] = intval($item['price'] * 100);
$item['flows'] = floatval($item['flows']);
}
$attributes['minimum_settings'] = $minimum_settings;
if ($attributes['start_at'] > $attributes['end_at']) {
throw new InvalidArgumentException('开始时间必须小于结束时间');
}
$rule = [
'pool_id' => ['required', Rule::exists($this->flowPoolRepository->getTable(), 'id')],
'gradient_price' => ['required', 'integer'],
'gradient' => ['required', 'integer'],
'gradient_unit' => ['required', 'in:0,1'],
];
$message = [
'pool_id.required' => '请输入流量池ID',
'gradient_price.required' => '请输入次月单价',
'gradient.required' => '请输入梯度',
'gradient_unit.required' => '请选择梯度单位',
];
Validator::validate($attributes, $rule, $message);
if (!$attributes['id']) {
DB::transaction(function () use ($attributes) {
$settings = $this->flowPoolSettingRepository->withConditions(['pool_id' => $attributes['pool_id']])->get();
$creates = [];
if (!$settings->isEmpty()) {
foreach ($settings as $item) {
$result = range_compare([$attributes['start_at'], $attributes['end_at']], [$item['start_at'], $item['end_at']]);
switch ($result) {
case 0:
$this->flowPoolSettingRepository->where(['id' => $item['id']])->update($attributes);
$creates = [];
break 2;
case 1:
$this->flowPoolSettingRepository->destroy($item['id']);
$creates[0] = $attributes;
break;
case 2:
$this->flowPoolSettingRepository->destroy($item['id']);
$creates[0] = $attributes;
unset($item['id']);
$creates[1] = $item->toArray();
$creates[1]['end_at'] = Carbon::parse($attributes['start_at'])->subMonth()->endOfMonth()->format('Y-m-d H:i:s');
$creates[2] = $item->toArray();
$creates[2]['start_at'] = Carbon::parse($attributes['end_at'])->addMonth()->startOfMonth()->format('Y-m-d H:i:s');
break 2;
case 3:
case 4:
$creates[0] = $attributes;
break;
case 5:
$creates[0] = $attributes;
$item['start_at'] = Carbon::parse($attributes['end_at'])->addMonth()->startOfMonth()->format('Y-m-d H:i:s');
$this->flowPoolSettingRepository->where(['id' => $item['id']])->update($item->toArray());
break;
case 6:
$creates[0] = $attributes;
$item['end_at'] = Carbon::parse($attributes['start_at'])->subMonth()->startOfMonth()->format('Y-m-d H:i:s');
$this->flowPoolSettingRepository->where(['id' => $item['id']])->update($item->toArray());
break;
default:
$creates[0] = $attributes;
break;
}
}
} else {
$creates[0] = $attributes;
$creates[0]['start_at'] = '2000-01-01 00:00:00';
$creates[0]['end_at'] !== '3000-01-01 23:59:59';
}
$maxId = FlowPoolSetting::withTrashed()->max('id') ?? 0;
foreach ($creates as $key => $create) {
$create['id'] = ++$maxId;
if ($key) {
$this->flowPoolSettingRepository->create($create);
} else {
$node =$this->flowPoolSettingRepository->create($create);
}
}
});
}
if ($attributes['id']) {
$node = $this->flowPoolSettingRepository->find($attributes['id']);
if (!$node) {
throw new NotExistException('规则不存在或已删除');
}
if ($node->start_at !== $attributes['start_at'] || $node->end_at !== $attributes['end_at']) {
throw new InvalidArgumentException('起止时间不能修改');
}
$this->flowPoolSettingRepository->setModel($node)->update($attributes);
}
return $node;
}
/**
* 数据生成
*
* $conditions['pool_id'];
* $conditions['month'];
* $conditions['total_flows'];
* $conditions['settings'];
* $conditions['settings'][*]['package_id'];
* $conditions['settings'][*]['total'];
* $conditions['settings'][*]['cards'];
* $conditions['settings'][*]['cards'][*]['counts'];
* $conditions['settings'][*]['cards'][*]['flow_range'];
*
* @return void
*/
public function flows(array $conditions)
{
$conditions = array_only($conditions, ['pool_id', 'month', 'total_flows', 'settings']);
$conditions['total_flows'] = floatval($conditions['total_flows']);
$settings = $conditions['settings'];
$min_total_flows = 0;
$max_total_flows = 0;
foreach ($settings as &$setting) {
$setting = array_only($setting, ['package_id', 'package_name', 'total', 'cards']);
$cards = $setting['cards'];
foreach ($cards as &$card) {
$card = array_only($card, ['counts', 'flow_range']);
$card['flow_range'][0] = floatval($card['flow_range'][0]);
$card['flow_range'][1] = floatval($card['flow_range'][1]);
$min_total_flows += $card['counts'] * $card['flow_range'][0];
$max_total_flows += $card['counts'] * $card['flow_range'][1];
}
$setting['cards'] = $cards;
}
unset($setting);
$conditions['settings'] = $settings;
if ($min_total_flows > $conditions['total_flows'] || $max_total_flows < $conditions['total_flows']) {
throw new NotAllowedException('流量范围设置不正确,最小流量大于总流量或最大流量小于总流量');
}
$pool_id = $conditions['pool_id'];
$month = Carbon::parse($conditions['month']);
$settings = array_keyBy($conditions['settings'], 'package_id');
if (!$flowPool = $this->flowPoolRepository->find($pool_id)) {
throw new NotExistException('流量池不存在或已删除');
}
$flowPool = $this->transformer(collect([$flowPool]), $month)->first();
if (!$flowPool->setting_status) {
throw new NotAllowedException('当前月份计费规则未设置或套餐保底流量未设置');
}
$cardConditions = [
'type' => [0, 1, 2],
'month' => $month,
'company_id' => $flowPool->company_id,
'package_id' => array_keys($settings),
'unit_price' => 0
];
$cards = app(OrderCardPartitionRepository::class)->selectRaw('distinct sim as sim, package_id')->withConditions($cardConditions)->get();
$allTotal = $cards->count();
$cards = $cards->groupBy('package_id')->toArray();
$data = [];
foreach ($cards as $package_id => $array) {
if (!$setting = $settings[$package_id]) {
throw new NotAllowedException("套餐 ID:{$package_id} 未设置");
}
$total = array_sum(array_pluck($setting['cards'], 'counts'));
if ($total !== count($array)) {
throw new NotAllowedException("套餐 ID:{$package_id} 数量设置不匹配");
}
$offset = 0;
foreach ($setting['cards'] as $cardSetting) {
$simArray = array_slice($array, $offset, $cardSetting['counts']);
$offset += $cardSetting['counts'];
$range_start = $cardSetting['flow_range'][0];
$range_end = $cardSetting['flow_range'][1];
if ($range_end < $range_start) {
throw new NotAllowedException("流量范围结束必须不小于开始");
}
$dataItems = [];
$flows = $conditions['total_flows'] * $cardSetting['counts'] / $allTotal;
foreach ($simArray as $sim) {
$mebibyte = rand($range_start * 100, $range_end * 100) / 100;
$rowObj = [
'month' => $month->format('Ym'),
'sim' => $sim['sim'],
'package_id' => $package_id,
'pool_id' => $pool_id,
'mebibyte' => $mebibyte,
];
$dataItems[] = $rowObj;
}
$itemMebibyte = array_sum(array_pluck($dataItems, 'mebibyte'));
if ($itemMebibyte) {
$k = $flows/$itemMebibyte;
foreach ($dataItems as &$value) {
$value['mebibyte'] = round($value['mebibyte'] * $k);
}
}
$data = array_merge($data, $dataItems);
}
}
$table = self::checkTable($month->format('Ym'));
$flowPoolData = $conditions;
$flowPoolData['month'] = $month->format('Ym');
FlowPoolData::updateOrCreate(array_only($flowPoolData, ['pool_id', 'month']), $flowPoolData);
DB::transaction(function () use ($table, $data, $pool_id) {
try {
app(FlowPoolMonth::class)->setTable($table)->where('pool_id', $pool_id)->delete();
foreach (array_chunk($data, 10000) as $chunk) {
app(FlowPoolMonth::class)->setTable($table)->upsert($chunk, ['sim', 'month']);
}
} catch (\Exception $e) {
throw $e;
}
});
return true;
}
/**
* 流量池卡详情
*
* @param array $conditions
* @return void
*/
public function show(array $conditions = [])
{
$limit = $conditions['limit'] ?? 20;
$page = $conditions['page'] ?? 1;
$pool_id = $conditions['pool_id'];
$month = Carbon::parse($conditions['month']);
if (!$flowPool = $this->flowPoolRepository->find($pool_id)) {
throw new NotExistException('流量池不存在或已删除');
}
$flowPool = $this->transformer(collect([$flowPool]), $month)->first();
$minimum_settings = $flowPool['settings'][0]['minimum_settings'] ?? [];
$minimum_flows = array_pluck($minimum_settings, 'flows', 'package_id');
$table = app(FlowPoolMonth::class)->getTable() . '_' . $month->format('Ym');
$query = app(FlowPoolMonth::class)->setTable($table)->where('pool_id', $pool_id);
if (Schema::hasTable($table) && $total = $query->count()) {
$cards = $query->select(['package_id', 'sim', 'mebibyte'])->forPage($page, $limit)->get();
} else {
$cards = collect();
}
$cards->map(function ($item) use ($minimum_flows) {
$item->package_name = PackageService::load($item->package_id)['name'];
$item->mebibyte = $this->humanFlows($item->mebibyte);
$item->minimum_flows = $this->humanFlows($minimum_flows[$item->package_id] ?? 0);
});
$cards = new LengthAwarePaginator($cards, $total, $limit);
$flowPool = $this->transformerSettings($flowPool);
return ['flowPool' => $flowPool, 'cards' => $cards];
}
/**
* 流量池格式转化
*
* @param mixed $flowPools
* @param Carbon $month
* @return void
*/
public static function transformer($flowPools, $month)
{
$allSettings = app(FlowPoolSettingRepository::class)->withConditions(['pool_id' => $flowPools->pluck('id')->toArray()])
->orderBy('start_at', 'desc')->get()->groupBy('pool_id');
$carrierOperators = app(Dicts::class)->get('carrier_operator');
$shares = app(Dicts::class)->get('shares');
$flowPools->map(function ($item) use ($carrierOperators, $shares, $allSettings, $month) {
$setting_status = 0;
$item->company_name = CompanyService::load($item['company_id'])['name'];
$item->carrier_operator_name = $carrierOperators[$item['carrier_operator']];
$item->shared_name = $shares[$item['shared']];
$item->real_pool_ids = empty($item->real_pool_ids) ? [] : $item->real_pool_ids;
$item->package_ids = empty($item->package_ids) ? [] : $item->package_ids;
$item->status = $item->end_at ? 1 : 0;
$packages = [];
foreach ($item->package_ids as $value) {
$package = PackageService::load($value);
$packages[] = [
'type' => $package['type'],
'package_id' => $value,
'package_name' => $package['name'],
'carrier_operator' => $package['carrier_operator'],
];
}
$item->packages = $packages;
$packages = array_keyBy($packages, 'package_id');
if ($settings = $allSettings[$item->id]) {
foreach ($settings as $setting) {
$minimum_settings = array_keyBy($setting->minimum_settings ?? [], 'package_id');
if ($setting->start_at <= $month && $setting->end_at >= $month) {
$setting_status = 1;
foreach ($packages as $package) {
if (!isset($minimum_settings[$package['package_id']])) {
$setting_status = 0;
$minimum_settings[$package['package_id']] = [
'package_id' => $package['package_id'],
'package_name' => PackageService::load($package['package_id'])['name'],
'flows' => 0,
'price' => 0
];
}
}
foreach ($minimum_settings as $package_id => $minimum_setting) {
if (!isset($packages[$package_id])) {
unset($minimum_settings[$package_id]);
}
}
}
foreach ($minimum_settings as $package_id => $minimum_setting) {
$minimum_settings[$package_id]['package_name'] = PackageService::load($package_id)['name'];
}
$setting->minimum_settings = array_values($minimum_settings);
}
} else {
$settings = collect();
$setting_status = 0;
}
$item->settings = $settings;
$item->setting_status = $setting_status;
});
return $flowPools;
}
/**
* 流量池格式转化
*
* @param mixed $item
* @return void
*/
public static function transformerSettings($item)
{
$settings = $item->settings;
foreach ($settings as &$setting) {
$setting['gradient_price'] = sprintf('%.02f', $setting['gradient_price']/100);
$setting['gradient'] = sprintf('%.02f', $setting['gradient']);
$minimum_settings = $setting['minimum_settings'] ?? [];
foreach ($minimum_settings as &$minimum_setting) {
$minimum_setting['price'] = sprintf('%.02f', $minimum_setting['price']/100);
$minimum_setting['flows'] = floatval($minimum_setting['flows']);
}
$setting['minimum_settings'] = $minimum_settings;
}
$item->settings = $settings;
return $item;
}
public static function sn($id)
{
return sprintf('FP%011d', $id);
}
public static function load($id)
{
if (!self::$pools) {
self::$pools = app(FlowPoolRepository::class)->withTrashed()->get()->keyBy('id');
}
return self::$pools[$id];
}
public static function humanFlows($int)
{
return human_filesize($int, 2, ['unit' => 'MB', 'min' => 'MB', 'max' => 'MB']);
}
/**
* 检查当月分区表是否已创建
*
* @param string $month
* @return string
*/
public static function checkTable(string $month)
{
$table = app(FlowPoolMonth::class)->getTable() . '_' . $month;
if (!Schema::hasTable($table)) {
Schema::table(app(FlowPoolMonth::class)->getTable(), function ($blueprint) use ($table, $month) {
$blueprint->addPartition($table, 'list', [$month]);
});
Schema::table($table, function ($blueprint) {
$blueprint->unique(['sim', 'month']);
});
}
return $table;
}
}