Initial commit

This commit is contained in:
xboard
2023-11-17 14:44:01 +08:00
commit 65fe7682ff
460 changed files with 63554 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\ConfigSave;
use App\Jobs\SendEmailJob;
use App\Models\Setting;
use App\Services\TelegramService;
use App\Utils\Dict;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class ConfigController extends Controller
{
public function getEmailTemplate()
{
$path = resource_path('views/mail/');
$files = array_map(function ($item) use ($path) {
return str_replace($path, '', $item);
}, glob($path . '*'));
return response([
'data' => $files
]);
}
public function getThemeTemplate()
{
$path = public_path('theme/');
$files = array_map(function ($item) use ($path) {
return str_replace($path, '', $item);
}, glob($path . '*'));
return response([
'data' => $files
]);
}
public function testSendMail(Request $request)
{
$obj = new SendEmailJob([
'email' => $request->user['email'],
'subject' => 'This is xboard test email',
'template_name' => 'notify',
'template_value' => [
'name' => admin_setting('app_name', 'XBoard'),
'content' => 'This is xboard test email',
'url' => admin_setting('app_url')
]
]);
return response([
'data' => true,
'log' => $obj->handle()
]);
}
public function setTelegramWebhook(Request $request)
{
$hookUrl = url('/api/v1/guest/telegram/webhook?access_token=' . md5(admin_setting('telegram_bot_token', $request->input('telegram_bot_token'))));
$telegramService = new TelegramService($request->input('telegram_bot_token'));
$telegramService->getMe();
$telegramService->setWebhook($hookUrl);
return response([
'data' => true
]);
}
public function fetch(Request $request)
{
$key = $request->input('key');
$data = [
'invite' => [
'invite_force' => (int)admin_setting('invite_force', 0),
'invite_commission' => admin_setting('invite_commission', 10),
'invite_gen_limit' => admin_setting('invite_gen_limit', 5),
'invite_never_expire' => admin_setting('invite_never_expire', 0),
'commission_first_time_enable' => admin_setting('commission_first_time_enable', 1),
'commission_auto_check_enable' => admin_setting('commission_auto_check_enable', 1),
'commission_withdraw_limit' => admin_setting('commission_withdraw_limit', 100),
'commission_withdraw_method' => admin_setting('commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT),
'withdraw_close_enable' => admin_setting('withdraw_close_enable', 0),
'commission_distribution_enable' => admin_setting('commission_distribution_enable', 0),
'commission_distribution_l1' => admin_setting('commission_distribution_l1'),
'commission_distribution_l2' => admin_setting('commission_distribution_l2'),
'commission_distribution_l3' => admin_setting('commission_distribution_l3')
],
'site' => [
'logo' => admin_setting('logo'),
'force_https' => (int)admin_setting('force_https', 0),
'stop_register' => (int)admin_setting('stop_register', 0),
'app_name' => admin_setting('app_name', 'XBoard'),
'app_description' => admin_setting('app_description', 'XBoard is best!'),
'app_url' => admin_setting('app_url'),
'subscribe_url' => admin_setting('subscribe_url'),
'try_out_plan_id' => (int)admin_setting('try_out_plan_id', 0),
'try_out_hour' => (int)admin_setting('try_out_hour', 1),
'tos_url' => admin_setting('tos_url'),
'currency' => admin_setting('currency', 'CNY'),
'currency_symbol' => admin_setting('currency_symbol', '¥'),
],
'subscribe' => [
'plan_change_enable' => (int)admin_setting('plan_change_enable', 1),
'reset_traffic_method' => (int)admin_setting('reset_traffic_method', 0),
'surplus_enable' => (int)admin_setting('surplus_enable', 1),
'new_order_event_id' => (int)admin_setting('new_order_event_id', 0),
'renew_order_event_id' => (int)admin_setting('renew_order_event_id', 0),
'change_order_event_id' => (int)admin_setting('change_order_event_id', 0),
'show_info_to_server_enable' => (int)admin_setting('show_info_to_server_enable', 0),
'show_protocol_to_server_enable' => (int)admin_setting('show_protocol_to_server_enable', 0),
],
'frontend' => [
'frontend_theme' => admin_setting('frontend_theme', 'Xboard'),
'frontend_theme_sidebar' => admin_setting('frontend_theme_sidebar', 'light'),
'frontend_theme_header' => admin_setting('frontend_theme_header', 'dark'),
'frontend_theme_color' => admin_setting('frontend_theme_color', 'default'),
'frontend_background_url' => admin_setting('frontend_background_url'),
],
'server' => [
'server_token' => admin_setting('server_token'),
'server_pull_interval' => admin_setting('server_pull_interval', 60),
'server_push_interval' => admin_setting('server_push_interval', 60),
],
'email' => [
'email_template' => admin_setting('email_template', 'default'),
'email_host' => admin_setting('email_host'),
'email_port' => admin_setting('email_port'),
'email_username' => admin_setting('email_username'),
'email_password' => admin_setting('email_password'),
'email_encryption' => admin_setting('email_encryption'),
'email_from_address' => admin_setting('email_from_address')
],
'telegram' => [
'telegram_bot_enable' => admin_setting('telegram_bot_enable', 0),
'telegram_bot_token' => admin_setting('telegram_bot_token'),
'telegram_discuss_link' => admin_setting('telegram_discuss_link')
],
'app' => [
'windows_version' => admin_setting('windows_version'),
'windows_download_url' => admin_setting('windows_download_url'),
'macos_version' => admin_setting('macos_version'),
'macos_download_url' => admin_setting('macos_download_url'),
'android_version' => admin_setting('android_version'),
'android_download_url' => admin_setting('android_download_url')
],
'safe' => [
'email_verify' => (int)admin_setting('email_verify', 0),
'safe_mode_enable' => (int)admin_setting('safe_mode_enable', 0),
'secure_path' => admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key')))),
'email_whitelist_enable' => (int)admin_setting('email_whitelist_enable', 0),
'email_whitelist_suffix' => admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT),
'email_gmail_limit_enable' => admin_setting('email_gmail_limit_enable', 0),
'recaptcha_enable' => (int)admin_setting('recaptcha_enable', 0),
'recaptcha_key' => admin_setting('recaptcha_key'),
'recaptcha_site_key' => admin_setting('recaptcha_site_key'),
'register_limit_by_ip_enable' => (int)admin_setting('register_limit_by_ip_enable', 0),
'register_limit_count' => admin_setting('register_limit_count', 3),
'register_limit_expire' => admin_setting('register_limit_expire', 60),
'password_limit_enable' => (int)admin_setting('password_limit_enable', 1),
'password_limit_count' => admin_setting('password_limit_count', 5),
'password_limit_expire' => admin_setting('password_limit_expire', 60)
]
];
if ($key && isset($data[$key])) {
return response([
'data' => [
$key => $data[$key]
]
]);
};
// TODO: default should be in Dict
return response([
'data' => $data
]);
}
public function save(ConfigSave $request)
{
$data = $request->validated();
$config = config('v2board');
foreach (ConfigSave::RULES as $k => $v) {
if (!in_array($k, array_keys(ConfigSave::RULES))) {
unset($config[$k]);
continue;
}
if (array_key_exists($k, $data)) {
$value = $data[$k];
if (is_array($value)) $value = json_encode($value);
Setting::updateOrCreate(
['name' => $k],
['name' => $k, 'value' => $value]
);
}
}
Cache::forget('admin_settings');
// \Artisan::call('horizon:terminate'); //重启队列使配置生效
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\CouponGenerate;
use App\Http\Requests\Admin\CouponSave;
use App\Models\Coupon;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class CouponController extends Controller
{
public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'id';
$builder = Coupon::orderBy($sort, $sortType);
$total = $builder->count();
$coupons = $builder->forPage($current, $pageSize)
->get();
return response([
'data' => $coupons,
'total' => $total
]);
}
public function show(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$coupon = Coupon::find($request->input('id'));
if (!$coupon) {
abort(500, '优惠券不存在');
}
$coupon->show = $coupon->show ? 0 : 1;
if (!$coupon->save()) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function generate(CouponGenerate $request)
{
if ($request->input('generate_count')) {
$this->multiGenerate($request);
return;
}
$params = $request->validated();
if (!$request->input('id')) {
if (!isset($params['code'])) {
$params['code'] = Helper::randomChar(8);
}
if (!Coupon::create($params)) {
abort(500, '创建失败');
}
} else {
try {
Coupon::find($request->input('id'))->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
private function multiGenerate(CouponGenerate $request)
{
$coupons = [];
$coupon = $request->validated();
$coupon['created_at'] = $coupon['updated_at'] = time();
$coupon['show'] = 1;
unset($coupon['generate_count']);
for ($i = 0;$i < $request->input('generate_count');$i++) {
$coupon['code'] = Helper::randomChar(8);
array_push($coupons, $coupon);
}
DB::beginTransaction();
if (!Coupon::insert(array_map(function ($item) use ($coupon) {
// format data
if (isset($item['limit_plan_ids']) && is_array($item['limit_plan_ids'])) {
$item['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
}
if (isset($item['limit_period']) && is_array($item['limit_period'])) {
$item['limit_period'] = json_encode($coupon['limit_period']);
}
return $item;
}, $coupons))) {
DB::rollBack();
abort(500, '生成失败');
}
DB::commit();
$data = "名称,类型,金额或比例,开始时间,结束时间,可用次数,可用于订阅,券码,生成时间\r\n";
foreach($coupons as $coupon) {
$type = ['', '金额', '比例'][$coupon['type']];
$value = ['', ($coupon['value'] / 100),$coupon['value']][$coupon['type']];
$startTime = date('Y-m-d H:i:s', $coupon['started_at']);
$endTime = date('Y-m-d H:i:s', $coupon['ended_at']);
$limitUse = $coupon['limit_use'] ?? '不限制';
$createTime = date('Y-m-d H:i:s', $coupon['created_at']);
$limitPlanIds = isset($coupon['limit_plan_ids']) ? implode("/", $coupon['limit_plan_ids']) : '不限制';
$data .= "{$coupon['name']},{$type},{$value},{$startTime},{$endTime},{$limitUse},{$limitPlanIds},{$coupon['code']},{$createTime}\r\n";
}
echo $data;
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$coupon = Coupon::find($request->input('id'));
if (!$coupon) {
abort(500, '优惠券不存在');
}
if (!$coupon->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\KnowledgeSave;
use App\Http\Requests\Admin\KnowledgeSort;
use App\Models\Knowledge;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class KnowledgeController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$knowledge = Knowledge::find($request->input('id'))->toArray();
if (!$knowledge) abort(500, '知识不存在');
return response([
'data' => $knowledge
]);
}
return response([
'data' => Knowledge::select(['title', 'id', 'updated_at', 'category', 'show'])
->orderBy('sort', 'ASC')
->get()
]);
}
public function getCategory(Request $request)
{
return response([
'data' => array_keys(Knowledge::get()->groupBy('category')->toArray())
]);
}
public function save(KnowledgeSave $request)
{
$params = $request->validated();
if (!$request->input('id')) {
if (!Knowledge::create($params)) {
abort(500, '创建失败');
}
} else {
try {
Knowledge::find($request->input('id'))->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function show(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$knowledge = Knowledge::find($request->input('id'));
if (!$knowledge) {
abort(500, '知识不存在');
}
$knowledge->show = $knowledge->show ? 0 : 1;
if (!$knowledge->save()) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function sort(KnowledgeSort $request)
{
DB::beginTransaction();
try {
foreach ($request->input('knowledge_ids') as $k => $v) {
$knowledge = Knowledge::find($v);
$knowledge->timestamps = false;
$knowledge->update(['sort' => $k + 1]);
}
} catch (\Exception $e) {
DB::rollBack();
abort(500, '保存失败');
}
DB::commit();
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$knowledge = Knowledge::find($request->input('id'));
if (!$knowledge) {
abort(500, '知识不存在');
}
if (!$knowledge->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,81 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\NoticeSave;
use App\Models\Notice;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class NoticeController extends Controller
{
public function fetch(Request $request)
{
return response([
'data' => Notice::orderBy('id', 'DESC')->get()
]);
}
public function save(NoticeSave $request)
{
$data = $request->only([
'title',
'content',
'img_url',
'tags'
]);
if (!$request->input('id')) {
if (!Notice::create($data)) {
abort(500, '保存失败');
}
} else {
try {
Notice::find($request->input('id'))->update($data);
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function show(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$notice = Notice::find($request->input('id'));
if (!$notice) {
abort(500, '公告不存在');
}
$notice->show = $notice->show ? 0 : 1;
if (!$notice->save()) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$notice = Notice::find($request->input('id'));
if (!$notice) {
abort(500, '公告不存在');
}
if (!$notice->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,190 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\OrderAssign;
use App\Http\Requests\Admin\OrderFetch;
use App\Http\Requests\Admin\OrderUpdate;
use App\Models\CommissionLog;
use App\Models\Order;
use App\Models\Plan;
use App\Models\User;
use App\Services\OrderService;
use App\Services\UserService;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class OrderController extends Controller
{
private function filter(Request $request, &$builder)
{
if ($request->input('filter')) {
foreach ($request->input('filter') as $filter) {
if ($filter['key'] === 'email') {
$user = User::where('email', "%{$filter['value']}%")->first();
if (!$user) continue;
$builder->where('user_id', $user->id);
continue;
}
if ($filter['condition'] === '模糊') {
$filter['condition'] = 'like';
$filter['value'] = "%{$filter['value']}%";
}
$builder->where($filter['key'], $filter['condition'], $filter['value']);
}
}
}
public function detail(Request $request)
{
$order = Order::find($request->input('id'));
if (!$order) abort(500, '订单不存在');
$order['commission_log'] = CommissionLog::where('trade_no', $order->trade_no)->get();
if ($order->surplus_order_ids) {
$order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get();
}
return response([
'data' => $order
]);
}
public function fetch(OrderFetch $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$orderModel = Order::orderBy('created_at', 'DESC');
if ($request->input('is_commission')) {
$orderModel->where('invite_user_id', '!=', NULL);
$orderModel->whereNotIn('status', [0, 2]);
$orderModel->where('commission_balance', '>', 0);
}
$this->filter($request, $orderModel);
$total = $orderModel->count();
$res = $orderModel->forPage($current, $pageSize)
->get();
$plan = Plan::get();
for ($i = 0; $i < count($res); $i++) {
for ($k = 0; $k < count($plan); $k++) {
if ($plan[$k]['id'] == $res[$i]['plan_id']) {
$res[$i]['plan_name'] = $plan[$k]['name'];
}
}
}
return response([
'data' => $res,
'total' => $total
]);
}
public function paid(Request $request)
{
$order = Order::where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
if ($order->status !== 0) abort(500, '只能对待支付的订单进行操作');
$orderService = new OrderService($order);
if (!$orderService->paid('manual_operation')) {
abort(500, '更新失败');
}
return response([
'data' => true
]);
}
public function cancel(Request $request)
{
$order = Order::where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
if ($order->status !== 0) abort(500, '只能对待支付的订单进行操作');
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
abort(500, '更新失败');
}
return response([
'data' => true
]);
}
public function update(OrderUpdate $request)
{
$params = $request->only([
'commission_status'
]);
$order = Order::where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, '订单不存在');
}
try {
$order->update($params);
} catch (\Exception $e) {
abort(500, '更新失败');
}
return response([
'data' => true
]);
}
public function assign(OrderAssign $request)
{
$plan = Plan::find($request->input('plan_id'));
$user = User::where('email', $request->input('email'))->first();
if (!$user) {
abort(500, '该用户不存在');
}
if (!$plan) {
abort(500, '该订阅不存在');
}
$userService = new UserService();
if ($userService->isNotCompleteOrderByUserId($user->id)) {
abort(500, '该用户还有待支付的订单,无法分配');
}
DB::beginTransaction();
$order = new Order();
$orderService = new OrderService($order);
$order->user_id = $user->id;
$order->plan_id = $plan->id;
$order->period = $request->input('period');
$order->trade_no = Helper::guid();
$order->total_amount = $request->input('total_amount');
if ($order->period === 'reset_price') {
$order->type = 4;
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
$order->type = 3;
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
$order->type = 2;
} else {
$order->type = 1;
}
$orderService->setInvite($user);
if (!$order->save()) {
DB::rollback();
abort(500, '订单创建失败');
}
DB::commit();
return response([
'data' => $order->trade_no
]);
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\PaymentSave;
use App\Models\Payment;
use App\Services\PaymentService;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class PaymentController extends Controller
{
public function getPaymentMethods()
{
$methods = [];
foreach (glob(base_path('app//Payments') . '/*.php') as $file) {
array_push($methods, pathinfo($file)['filename']);
}
return response([
'data' => $methods
]);
}
public function fetch()
{
$payments = Payment::orderBy('sort', 'ASC')->get();
foreach ($payments as $k => $v) {
$notifyUrl = url("/api/v1/guest/payment/notify/{$v->payment}/{$v->uuid}");
if ($v->notify_domain) {
$parseUrl = parse_url($notifyUrl);
$notifyUrl = $v->notify_domain . $parseUrl['path'];
}
$payments[$k]['notify_url'] = $notifyUrl;
}
return response([
'data' => $payments
]);
}
public function getPaymentForm(Request $request)
{
$paymentService = new PaymentService($request->input('payment'), $request->input('id'));
return response([
'data' => $paymentService->form()
]);
}
public function show(Request $request)
{
$payment = Payment::find($request->input('id'));
if (!$payment) abort(500, '支付方式不存在');
$payment->enable = !$payment->enable;
if (!$payment->save()) abort(500, '保存失败');
return response([
'data' => true
]);
}
public function save(Request $request)
{
if (!admin_setting('app_url')) {
abort(500, '请在站点配置中配置站点地址');
}
$params = $request->validate([
'name' => 'required',
'icon' => 'nullable',
'payment' => 'required',
'config' => 'required',
'notify_domain' => 'nullable|url',
'handling_fee_fixed' => 'nullable|integer',
'handling_fee_percent' => 'nullable|numeric|between:0.1,100'
], [
'name.required' => '显示名称不能为空',
'payment.required' => '网关参数不能为空',
'config.required' => '配置参数不能为空',
'notify_domain.url' => '自定义通知域名格式有误',
'handling_fee_fixed.integer' => '固定手续费格式有误',
'handling_fee_percent.between' => '百分比手续费范围须在0.1-100之间'
]);
if ($request->input('id')) {
$payment = Payment::find($request->input('id'));
if (!$payment) abort(500, '支付方式不存在');
try {
$payment->update($params);
} catch (\Exception $e) {
abort(500, $e->getMessage());
}
return response([
'data' => true
]);
}
$params['uuid'] = Helper::randomChar(8);
if (!Payment::create($params)) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
$payment = Payment::find($request->input('id'));
if (!$payment) abort(500, '支付方式不存在');
return response([
'data' => $payment->delete()
]);
}
public function sort(Request $request)
{
$request->validate([
'ids' => 'required|array'
], [
'ids.required' => '参数有误',
'ids.array' => '参数有误'
]);
DB::beginTransaction();
foreach ($request->input('ids') as $k => $v) {
if (!Payment::find($v)->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
}
DB::commit();
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\PlanSave;
use App\Http\Requests\Admin\PlanSort;
use App\Http\Requests\Admin\PlanUpdate;
use App\Models\Order;
use App\Models\Plan;
use App\Models\User;
use App\Services\PlanService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class PlanController extends Controller
{
public function fetch(Request $request)
{
$counts = PlanService::countActiveUsers();
$plans = Plan::orderBy('sort', 'ASC')->get();
foreach ($plans as $k => $v) {
$plans[$k]->count = 0;
foreach ($counts as $kk => $vv) {
if ($plans[$k]->id === $counts[$kk]->plan_id) $plans[$k]->count = $counts[$kk]->count;
}
}
return response([
'data' => $plans
]);
}
public function save(PlanSave $request)
{
$params = $request->validated();
if ($request->input('id')) {
$plan = Plan::find($request->input('id'));
if (!$plan) {
abort(500, '该订阅不存在');
}
DB::beginTransaction();
// update user group id and transfer
try {
if ($request->input('force_update')) {
User::where('plan_id', $plan->id)->update([
'group_id' => $params['group_id'],
'transfer_enable' => $params['transfer_enable'] * 1073741824,
'speed_limit' => $params['speed_limit']
]);
}
$plan->update($params);
} catch (\Exception $e) {
DB::rollBack();
abort(500, '保存失败');
}
DB::commit();
return response([
'data' => true
]);
}
if (!Plan::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (Order::where('plan_id', $request->input('id'))->first()) {
abort(500, '该订阅下存在订单无法删除');
}
if (User::where('plan_id', $request->input('id'))->first()) {
abort(500, '该订阅下存在用户无法删除');
}
if ($request->input('id')) {
$plan = Plan::find($request->input('id'));
if (!$plan) {
abort(500, '该订阅ID不存在');
}
}
return response([
'data' => $plan->delete()
]);
}
public function update(PlanUpdate $request)
{
$updateData = $request->only([
'show',
'renew'
]);
$plan = Plan::find($request->input('id'));
if (!$plan) {
abort(500, '该订阅不存在');
}
try {
$plan->update($updateData);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function sort(PlanSort $request)
{
DB::beginTransaction();
foreach ($request->input('plan_ids') as $k => $v) {
if (!Plan::find($v)->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
}
DB::commit();
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\V1\Admin\Server;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\ServerGroup;
use App\Models\ServerVmess;
use App\Models\User;
use App\Services\ServerService;
use Illuminate\Http\Request;
class GroupController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('group_id')) {
return response([
'data' => [ServerGroup::find($request->input('group_id'))]
]);
}
$serverGroups = ServerGroup::get();
$serverService = new ServerService();
$servers = $serverService->getAllServers();
foreach ($serverGroups as $k => $v) {
$serverGroups[$k]['user_count'] = User::where('group_id', $v['id'])->count();
$serverGroups[$k]['server_count'] = 0;
foreach ($servers as $server) {
if (in_array($v['id'], $server['group_id'])) {
$serverGroups[$k]['server_count'] = $serverGroups[$k]['server_count']+1;
}
}
}
return response([
'data' => $serverGroups
]);
}
public function save(Request $request)
{
if (empty($request->input('name'))) {
abort(500, '组名不能为空');
}
if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id'));
} else {
$serverGroup = new ServerGroup();
}
$serverGroup->name = $request->input('name');
return response([
'data' => $serverGroup->save()
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$serverGroup = ServerGroup::find($request->input('id'));
if (!$serverGroup) {
abort(500, '组不存在');
}
}
$servers = ServerVmess::all();
foreach ($servers as $server) {
if (in_array($request->input('id'), $server->group_id)) {
abort(500, '该组已被节点所使用,无法删除');
}
}
if (Plan::where('group_id', $request->input('id'))->first()) {
abort(500, '该组已被订阅所使用,无法删除');
}
if (User::where('group_id', $request->input('id'))->first()) {
abort(500, '该组已被用户所使用,无法删除');
}
return response([
'data' => $serverGroup->delete()
]);
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace App\Http\Controllers\V1\Admin\Server;
use App\Http\Controllers\Controller;
use App\Models\ServerHysteria;
use Illuminate\Http\Request;
class HysteriaController extends Controller
{
public function save(Request $request)
{
$params = $request->validate([
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'route_id' => 'nullable|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'tags' => 'nullable|array',
'excludes' => 'nullable|array',
'ips' => 'nullable|array',
'rate' => 'required|numeric',
'up_mbps' => 'required|numeric|min:1',
'down_mbps' => 'required|numeric|min:1',
'server_name' => 'nullable',
'insecure' => 'required|in:0,1',
'alpn' => 'nullable|in:0,1,2,3',
'version' => 'nullable|in:1,2',
'is_obfs' => 'nullable'
]);
if ($request->input('id')) {
$server = ServerHysteria::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
if (!ServerHysteria::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$server = ServerHysteria::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
}
return response([
'data' => $server->delete()
]);
}
public function update(Request $request)
{
$request->validate([
'show' => 'in:0,1'
], [
'show.in' => '显示状态格式不正确'
]);
$params = $request->only([
'show',
]);
$server = ServerHysteria::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function copy(Request $request)
{
$server = ServerHysteria::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!ServerHysteria::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\V1\Admin\Server;
use App\Http\Controllers\Controller;
use App\Services\ServerService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ManageController extends Controller
{
public function getNodes(Request $request)
{
$serverService = new ServerService();
return response([
'data' => $serverService->getAllServers()
]);
}
public function sort(Request $request)
{
ini_set('post_max_size', '1m');
$params = $request->only(
'shadowsocks',
'vmess',
'trojan',
'hysteria',
'vless'
) ?? [];
DB::beginTransaction();
foreach ($params as $k => $v) {
$model = 'App\\Models\\Server' . ucfirst($k);
foreach($v as $id => $sort) {
if (!$model::find($id)->update(['sort' => $sort])) {
DB::rollBack();
abort(500, '保存失败');
}
}
}
DB::commit();
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Http\Controllers\V1\Admin\Server;
use App\Http\Controllers\Controller;
use App\Models\ServerRoute;
use Illuminate\Http\Request;
class RouteController extends Controller
{
public function fetch(Request $request)
{
$routes = ServerRoute::get();
// TODO: remove on 1.8.0
foreach ($routes as $k => $route) {
$array = json_decode($route->match, true);
if (is_array($array)) $routes[$k]['match'] = $array;
}
// TODO: remove on 1.8.0
return [
'data' => $routes
];
}
public function save(Request $request)
{
$params = $request->validate([
'remarks' => 'required',
'match' => 'required|array',
'action' => 'required|in:block,dns',
'action_value' => 'nullable'
], [
'remarks.required' => '备注不能为空',
'match.required' => '匹配值不能为空',
'action.required' => '动作类型不能为空',
'action.in' => '动作类型参数有误'
]);
$params['match'] = array_filter($params['match']);
// TODO: remove on 1.8.0
$params['match'] = json_encode($params['match']);
// TODO: remove on 1.8.0
if ($request->input('id')) {
try {
$route = ServerRoute::find($request->input('id'));
$route->update($params);
return [
'data' => true
];
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
if (!ServerRoute::create($params)) abort(500, '创建失败');
return [
'data' => true
];
}
public function drop(Request $request)
{
$route = ServerRoute::find($request->input('id'));
if (!$route) abort(500, '路由不存在');
if (!$route->delete()) abort(500, '删除失败');
return [
'data' => true
];
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace App\Http\Controllers\V1\Admin\Server;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\ServerShadowsocksSave;
use App\Http\Requests\Admin\ServerShadowsocksUpdate;
use App\Models\ServerShadowsocks;
use Illuminate\Http\Request;
class ShadowsocksController extends Controller
{
public function save(ServerShadowsocksSave $request)
{
$params = $request->validated();
if ($request->input('id')) {
$server = ServerShadowsocks::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
if (!ServerShadowsocks::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$server = ServerShadowsocks::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
}
return response([
'data' => $server->delete()
]);
}
public function update(ServerShadowsocksUpdate $request)
{
$params = $request->only([
'show',
]);
$server = ServerShadowsocks::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function copy(Request $request)
{
$server = ServerShadowsocks::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!ServerShadowsocks::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace App\Http\Controllers\V1\Admin\Server;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\ServerTrojanSave;
use App\Http\Requests\Admin\ServerTrojanUpdate;
use App\Models\ServerTrojan;
use App\Services\ServerService;
use Illuminate\Http\Request;
class TrojanController extends Controller
{
public function save(ServerTrojanSave $request)
{
$params = $request->validated();
if ($request->input('id')) {
$server = ServerTrojan::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
if (!ServerTrojan::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$server = ServerTrojan::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
}
return response([
'data' => $server->delete()
]);
}
public function update(ServerTrojanUpdate $request)
{
$params = $request->only([
'show',
]);
$server = ServerTrojan::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function copy(Request $request)
{
$server = ServerTrojan::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!ServerTrojan::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
public function viewConfig(Request $request)
{
$serverService = new ServerService();
$config = $serverService->getTrojanConfig($request->input('node_id'), 23333);
return response([
'data' => $config
]);
}
}

View File

@@ -0,0 +1,127 @@
<?php
namespace App\Http\Controllers\V1\Admin\Server;
use App\Http\Controllers\Controller;
use App\Models\ServerVless;
use Illuminate\Http\Request;
use ParagonIE_Sodium_Compat as SodiumCompat;
use App\Utils\Helper;
class VlessController extends Controller
{
public function save(Request $request)
{
$params = $request->validate([
'group_id' => 'required',
'route_id' => 'nullable|array',
'name' => 'required',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'tls' => 'required|in:0,1,2',
'tls_settings' => 'nullable|array',
'flow' => 'nullable|in:xtls-rprx-vision',
'network' => 'required',
'network_settings' => 'nullable|array',
'tags' => 'nullable|array',
'excludes' => 'nullable|array',
'ips' => 'nullable|array',
'rate' => 'required',
'show' => 'nullable|in:0,1',
'sort' => 'nullable'
]);
if (isset($params['tls']) && (int)$params['tls'] === 2) {
$keyPair = SodiumCompat::crypto_box_keypair();
$params['tls_settings'] = $params['tls_settings'] ?? [];
if (!isset($params['tls_settings']['public_key'])) {
$params['tls_settings']['public_key'] = Helper::base64EncodeUrlSafe(SodiumCompat::crypto_box_publickey($keyPair));
}
if (!isset($params['tls_settings']['private_key'])) {
$params['tls_settings']['private_key'] = Helper::base64EncodeUrlSafe(SodiumCompat::crypto_box_secretkey($keyPair));
}
if (!isset($params['tls_settings']['short_id'])) {
$params['tls_settings']['short_id'] = substr(sha1($params['tls_settings']['private_key']), 0, 8);
}
if (!isset($params['tls_settings']['server_port'])) {
$params['tls_settings']['server_port'] = "443";
}
}
if ($request->input('id')) {
$server = ServerVless::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
if (!ServerVless::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$server = ServerVless::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
}
return response([
'data' => $server->delete()
]);
}
public function update(Request $request)
{
$params = $request->validate([
'show' => 'nullable|in:0,1',
]);
$server = ServerVless::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function copy(Request $request)
{
$server = ServerVless::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!ServerVless::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace App\Http\Controllers\V1\Admin\Server;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\ServerVmessSave;
use App\Http\Requests\Admin\ServerVmessUpdate;
use App\Models\ServerVmess;
use Illuminate\Http\Request;
class VmessController extends Controller
{
public function save(ServerVmessSave $request)
{
$params = $request->validated();
if ($request->input('id')) {
$server = ServerVmess::find($request->input('id'));
if (!$server) {
abort(500, '服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
if (!ServerVmess::create($params)) {
abort(500, '创建失败');
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if ($request->input('id')) {
$server = ServerVmess::find($request->input('id'));
if (!$server) {
abort(500, '节点ID不存在');
}
}
return response([
'data' => $server->delete()
]);
}
public function update(ServerVmessUpdate $request)
{
$params = $request->only([
'show',
]);
$server = ServerVmess::find($request->input('id'));
if (!$server) {
abort(500, '该服务器不存在');
}
try {
$server->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function copy(Request $request)
{
$server = ServerVmess::find($request->input('id'));
$server->show = 0;
if (!$server) {
abort(500, '服务器不存在');
}
if (!ServerVmess::create($server->toArray())) {
abort(500, '复制失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,209 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Models\CommissionLog;
use App\Models\Order;
use App\Models\ServerHysteria;
use App\Models\ServerVless;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Models\ServerVmess;
use App\Models\Stat;
use App\Models\StatServer;
use App\Models\StatUser;
use App\Models\Ticket;
use App\Models\User;
use App\Services\StatisticalService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class StatController extends Controller
{
public function getOverride(Request $request)
{
return [
'data' => [
'month_income' => Order::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->whereNotIn('status', [0, 2])
->sum('total_amount'),
'month_register_total' => User::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->count(),
'ticket_pending_total' => Ticket::where('status', 0)
->count(),
'commission_pending_total' => Order::where('commission_status', 0)
->where('invite_user_id', '!=', NULL)
->whereNotIn('status', [0, 2])
->where('commission_balance', '>', 0)
->count(),
'day_income' => Order::where('created_at', '>=', strtotime(date('Y-m-d')))
->where('created_at', '<', time())
->whereNotIn('status', [0, 2])
->sum('total_amount'),
'last_month_income' => Order::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
->where('created_at', '<', strtotime(date('Y-m-1')))
->whereNotIn('status', [0, 2])
->sum('total_amount'),
'commission_month_payout' => CommissionLog::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->sum('get_amount'),
'commission_last_month_payout' => CommissionLog::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
->where('created_at', '<', strtotime(date('Y-m-1')))
->sum('get_amount'),
]
];
}
public function getOrder(Request $request)
{
$statistics = Stat::where('record_type', 'd')
->limit(31)
->orderBy('record_at', 'DESC')
->get()
->toArray();
$result = [];
foreach ($statistics as $statistic) {
$date = date('m-d', $statistic['record_at']);
$result[] = [
'type' => '收款金额',
'date' => $date,
'value' => $statistic['paid_total'] / 100
];
$result[] = [
'type' => '收款笔数',
'date' => $date,
'value' => $statistic['paid_count']
];
$result[] = [
'type' => '佣金金额(已发放)',
'date' => $date,
'value' => $statistic['commission_total'] / 100
];
$result[] = [
'type' => '佣金笔数(已发放)',
'date' => $date,
'value' => $statistic['commission_count']
];
}
$result = array_reverse($result);
return [
'data' => $result
];
}
// 获取当日实时流量排行
public function getServerLastRank()
{
$servers = [
'shadowsocks' => ServerShadowsocks::with(['parent'])->get()->toArray(),
'v2ray' => ServerVmess::with(['parent'])->get()->toArray(),
'trojan' => ServerTrojan::with(['parent'])->get()->toArray(),
'vmess' => ServerVmess::with(['parent'])->get()->toArray(),
'hysteria' => ServerHysteria::with(['parent'])->get()->toArray(),
'vless' => ServerVless::with(['parent'])->get()->toArray(),
];
$recordAt = strtotime(date('Y-m-d'));
$statService = new StatisticalService();
$statService->setStartAt($recordAt);
$statService->setServerStats();
$stats = $statService->getStatServer();
$statistics = collect($stats)->map(function ($item){
$item['total'] = $item['u'] + $item['d'];
return $item;
})->sortByDesc('total')->values()->all();
// return json_encode($statistics);
foreach ($statistics as $k => $v) {
foreach ($servers[$v['server_type']] as $server) {
if ($server['id'] === $v['server_id']) {
$statistics[$k]['server_name'] = $server['name'];
if($server['parent']) $statistics[$k]['server_name'] .= "({$server['parent']['name']})";
}
}
$statistics[$k]['total'] = $statistics[$k]['total'] / 1073741824;
}
array_multisort(array_column($statistics, 'total'), SORT_DESC, $statistics);
return [
'data' => $statistics
];
}
// 获取昨日节点流量排行
public function getServerYesterdayRank()
{
$servers = [
'shadowsocks' => ServerShadowsocks::with(['parent'])->get()->toArray(),
'v2ray' => ServerVmess::with(['parent'])->get()->toArray(),
'trojan' => ServerTrojan::with(['parent'])->get()->toArray(),
'vmess' => ServerVmess::with(['parent'])->get()->toArray(),
'hysteria' => ServerHysteria::with(['parent'])->get()->toArray(),
'vless' => ServerVless::with(['parent'])->get()->toArray(),
];
$startAt = strtotime('-1 day', strtotime(date('Y-m-d')));
$endAt = strtotime(date('Y-m-d'));
$statistics = StatServer::select([
'server_id',
'server_type',
'u',
'd',
DB::raw('(u+d) as total')
])
->where('record_at', '>=', $startAt)
->where('record_at', '<', $endAt)
->where('record_type', 'd')
->limit(15)
->orderBy('total', 'DESC')
->get()
->toArray();
foreach ($statistics as $k => $v) {
foreach ($servers[$v['server_type']] as $server) {
if ($server['id'] === $v['server_id']) {
$statistics[$k]['server_name'] = $server['name'];
if($server['parent']) $statistics[$k]['server_name'] .= "({$server['parent']['name']})";
}
}
$statistics[$k]['total'] = $statistics[$k]['total'] / 1073741824;
}
array_multisort(array_column($statistics, 'total'), SORT_DESC, $statistics);
return [
'data' => $statistics
];
}
public function getStatUser(Request $request)
{
$request->validate([
'user_id' => 'required|integer'
]);
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$builder = StatUser::orderBy('record_at', 'DESC')->where('user_id', $request->input('user_id'));
$total = $builder->count();
$records = $builder->forPage($current, $pageSize)
->get();
// 追加当天流量
$recordAt = strtotime(date('Y-m-d'));
$statService = new StatisticalService();
$statService->setStartAt($recordAt);
$statService->setUserStats();
$todayTraffics = $statService->getStatUserByUserID($request->input('user_id'));
if (($current == 1) && count($todayTraffics) > 0) {
foreach ($todayTraffics as $todayTraffic){
$todayTraffic['server_rate'] = number_format($todayTraffic['server_rate'], 2);
$records->prepend($todayTraffic);
}
};
return [
'data' => $records,
'total' => $total + count($todayTraffics),
];
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Models\Log as LogModel;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Laravel\Horizon\Contracts\JobRepository;
use Laravel\Horizon\Contracts\MasterSupervisorRepository;
use Laravel\Horizon\Contracts\MetricsRepository;
use Laravel\Horizon\Contracts\SupervisorRepository;
use Laravel\Horizon\Contracts\WorkloadRepository;
use Laravel\Horizon\WaitTimeCalculator;
class SystemController extends Controller
{
public function getSystemStatus()
{
return response([
'data' => [
'schedule' => $this->getScheduleStatus(),
'horizon' => $this->getHorizonStatus(),
'schedule_last_runtime' => Cache::get(CacheKey::get('SCHEDULE_LAST_CHECK_AT', null))
]
]);
}
public function getQueueWorkload(WorkloadRepository $workload)
{
return response([
'data' => collect($workload->get())->sortBy('name')->values()->toArray()
]);
}
protected function getScheduleStatus():bool
{
return (time() - 120) < Cache::get(CacheKey::get('SCHEDULE_LAST_CHECK_AT', null));
}
protected function getHorizonStatus():bool
{
if (! $masters = app(MasterSupervisorRepository::class)->all()) {
return false;
}
return collect($masters)->contains(function ($master) {
return $master->status === 'paused';
}) ? false : true;
}
public function getQueueStats()
{
return response([
'data' => [
'failedJobs' => app(JobRepository::class)->countRecentlyFailed(),
'jobsPerMinute' => app(MetricsRepository::class)->jobsProcessedPerMinute(),
'pausedMasters' => $this->totalPausedMasters(),
'periods' => [
'failedJobs' => config('horizon.trim.recent_failed', config('horizon.trim.failed')),
'recentJobs' => config('horizon.trim.recent'),
],
'processes' => $this->totalProcessCount(),
'queueWithMaxRuntime' => app(MetricsRepository::class)->queueWithMaximumRuntime(),
'queueWithMaxThroughput' => app(MetricsRepository::class)->queueWithMaximumThroughput(),
'recentJobs' => app(JobRepository::class)->countRecent(),
'status' => $this->getHorizonStatus(),
'wait' => collect(app(WaitTimeCalculator::class)->calculate())->take(1),
]
]);
}
/**
* Get the total process count across all supervisors.
*
* @return int
*/
protected function totalProcessCount()
{
$supervisors = app(SupervisorRepository::class)->all();
return collect($supervisors)->reduce(function ($carry, $supervisor) {
return $carry + collect($supervisor->processes)->sum();
}, 0);
}
/**
* Get the number of master supervisors that are currently paused.
*
* @return int
*/
protected function totalPausedMasters()
{
if (! $masters = app(MasterSupervisorRepository::class)->all()) {
return 0;
}
return collect($masters)->filter(function ($master) {
return $master->status === 'paused';
})->count();
}
public function getSystemLog(Request $request) {
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('page_size') >= 10 ? $request->input('page_size') : 10;
$builder = LogModel::orderBy('created_at', 'DESC')
->setFilterAllowKeys('level');
$total = $builder->count();
$res = $builder->forPage($current, $pageSize)
->get();
return response([
'data' => $res,
'total' => $total
]);
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Services\ThemeService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
class ThemeController extends Controller
{
private $themes;
private $path;
public function __construct()
{
$this->path = $path = public_path('theme/');
$this->themes = array_map(function ($item) use ($path) {
return str_replace($path, '', $item);
}, glob($path . '*'));
}
public function getThemes()
{
$themeConfigs = [];
foreach ($this->themes as $theme) {
$themeConfigFile = $this->path . "{$theme}/config.json";
if (!File::exists($themeConfigFile)) continue;
$themeConfig = json_decode(File::get($themeConfigFile), true);
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) continue;
$themeConfigs[$theme] = $themeConfig;
if (admin_setting("theme_{$theme}")) continue;
$themeService = new ThemeService($theme);
$themeService->init();
}
return response([
'data' => [
'themes' => $themeConfigs,
'active' => admin_setting('frontend_theme', 'Xboard')
]
]);
}
public function getThemeConfig(Request $request)
{
$payload = $request->validate([
'name' => 'required|in:' . join(',', $this->themes)
]);
return response([
'data' => admin_setting("theme_{$payload['name']}") ?? config("theme.{$payload['name']}")
]);
}
public function saveThemeConfig(Request $request)
{
$payload = $request->validate([
'name' => 'required|in:' . join(',', $this->themes),
'config' => 'required'
]);
$payload['config'] = json_decode(base64_decode($payload['config']), true);
if (!$payload['config'] || !is_array($payload['config'])) abort(500, '参数有误');
$themeConfigFile = public_path("theme/{$payload['name']}/config.json");
if (!File::exists($themeConfigFile)) abort(500, '主题不存在');
$themeConfig = json_decode(File::get($themeConfigFile), true);
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) abort(500, '主题配置文件有误');
$validateFields = array_column($themeConfig['configs'], 'field_name');
$config = [];
foreach ($validateFields as $validateField) {
$config[$validateField] = isset($payload['config'][$validateField]) ? $payload['config'][$validateField] : '';
}
File::ensureDirectoryExists(base_path() . '/config/theme/');
// $data = var_export($config, 1);
try {
admin_setting(["theme_{$payload['name']}" => $config]);
// sleep(2);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => $config
]);
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
use App\Models\TicketMessage;
use App\Models\User;
use App\Services\TicketService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class TicketController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id'))
->first();
if (!$ticket) {
abort(500, '工单不存在');
}
$ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get();
for ($i = 0; $i < count($ticket['message']); $i++) {
if ($ticket['message'][$i]['user_id'] !== $ticket->user_id) {
$ticket['message'][$i]['is_me'] = true;
} else {
$ticket['message'][$i]['is_me'] = false;
}
}
return response([
'data' => $ticket
]);
}
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$model = Ticket::orderBy('updated_at', 'DESC');
if ($request->input('status') !== NULL) {
$model->where('status', $request->input('status'));
}
if ($request->input('reply_status') !== NULL) {
$model->whereIn('reply_status', $request->input('reply_status'));
}
if ($request->input('email') !== NULL) {
$user = User::where('email', $request->input('email'))->first();
if ($user) $model->where('user_id', $user->id);
}
$total = $model->count();
$res = $model->forPage($current, $pageSize)
->get();
return response([
'data' => $res,
'total' => $total
]);
}
public function reply(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
if (empty($request->input('message'))) {
abort(500, '消息不能为空');
}
$ticketService = new TicketService();
$ticketService->replyByAdmin(
$request->input('id'),
$request->input('message'),
$request->user['id']
);
return response([
'data' => true
]);
}
public function close(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$ticket = Ticket::where('id', $request->input('id'))
->first();
if (!$ticket) {
abort(500, '工单不存在');
}
$ticket->status = 1;
if (!$ticket->save()) {
abort(500, '关闭失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,293 @@
<?php
namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\UserFetch;
use App\Http\Requests\Admin\UserGenerate;
use App\Http\Requests\Admin\UserSendMail;
use App\Http\Requests\Admin\UserUpdate;
use App\Jobs\SendEmailJob;
use App\Models\Plan;
use App\Models\User;
use App\Services\AuthService;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class UserController extends Controller
{
public function resetSecret(Request $request)
{
$user = User::find($request->input('id'));
if (!$user) abort(500, '用户不存在');
$user->token = Helper::guid();
$user->uuid = Helper::guid(true);
return response([
'data' => $user->save()
]);
}
private function filter(Request $request, $builder)
{
$filters = $request->input('filter');
if ($filters) {
foreach ($filters as $k => $filter) {
if ($filter['condition'] === '模糊') {
$filter['condition'] = 'like';
$filter['value'] = "%{$filter['value']}%";
}
if ($filter['key'] === 'd' || $filter['key'] === 'transfer_enable') {
$filter['value'] = $filter['value'] * 1073741824;
}
if ($filter['key'] === 'invite_by_email') {
$user = User::where('email', $filter['condition'], $filter['value'])->first();
$inviteUserId = isset($user->id) ? $user->id : 0;
$builder->where('invite_user_id', $inviteUserId);
unset($filters[$k]);
continue;
}
$builder->where($filter['key'], $filter['condition'], $filter['value']);
}
}
}
public function fetch(UserFetch $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$userModel = User::select(
DB::raw('*'),
DB::raw('(u+d) as total_used')
)
->orderBy($sort, $sortType);
$this->filter($request, $userModel);
$total = $userModel->count();
$res = $userModel->forPage($current, $pageSize)
->get();
$plan = Plan::get();
for ($i = 0; $i < count($res); $i++) {
for ($k = 0; $k < count($plan); $k++) {
if ($plan[$k]['id'] == $res[$i]['plan_id']) {
$res[$i]['plan_name'] = $plan[$k]['name'];
}
}
$res[$i]['subscribe_url'] = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $res[$i]['token']);
}
return response([
'data' => $res,
'total' => $total
]);
}
public function getUserInfoById(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$user = User::find($request->input('id'));
if ($user->invite_user_id) {
$user['invite_user'] = User::find($user->invite_user_id);
}
return response([
'data' => $user
]);
}
public function update(UserUpdate $request)
{
$params = $request->validated();
$user = User::find($request->input('id'));
if (!$user) {
abort(500, '用户不存在');
}
if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) {
abort(500, '邮箱已被使用');
}
if (isset($params['password'])) {
$params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
$params['password_algo'] = NULL;
} else {
unset($params['password']);
}
if (isset($params['plan_id'])) {
$plan = Plan::find($params['plan_id']);
if (!$plan) {
abort(500, '订阅计划不存在');
}
$params['group_id'] = $plan->group_id;
}
if ($request->input('invite_user_email')) {
$inviteUser = User::where('email', $request->input('invite_user_email'))->first();
if ($inviteUser) {
$params['invite_user_id'] = $inviteUser->id;
}
} else {
$params['invite_user_id'] = null;
}
if (isset($params['banned']) && (int)$params['banned'] === 1) {
$authService = new AuthService($user);
$authService->removeAllSession();
}
try {
$user->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function dumpCSV(Request $request)
{
$userModel = User::orderBy('id', 'asc');
$this->filter($request, $userModel);
$res = $userModel->get();
$plan = Plan::get();
for ($i = 0; $i < count($res); $i++) {
for ($k = 0; $k < count($plan); $k++) {
if ($plan[$k]['id'] == $res[$i]['plan_id']) {
$res[$i]['plan_name'] = $plan[$k]['name'];
}
}
}
$data = "邮箱,余额,推广佣金,总流量,剩余流量,套餐到期时间,订阅计划,订阅地址\r\n";
foreach($res as $user) {
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$balance = $user['balance'] / 100;
$commissionBalance = $user['commission_balance'] / 100;
$transferEnable = $user['transfer_enable'] ? $user['transfer_enable'] / 1073741824 : 0;
$notUseFlow = (($user['transfer_enable'] - ($user['u'] + $user['d'])) / 1073741824) ?? 0;
$planName = $user['plan_name'] ?? '无订阅';
$subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']);
$data .= "{$user['email']},{$balance},{$commissionBalance},{$transferEnable},{$notUseFlow},{$expireDate},{$planName},{$subscribeUrl}\r\n";
}
echo "\xEF\xBB\xBF" . $data;
}
public function generate(UserGenerate $request)
{
if ($request->input('email_prefix')) {
if ($request->input('plan_id')) {
$plan = Plan::find($request->input('plan_id'));
if (!$plan) {
abort(500, '订阅计划不存在');
}
}
$user = [
'email' => $request->input('email_prefix') . '@' . $request->input('email_suffix'),
'plan_id' => isset($plan->id) ? $plan->id : NULL,
'group_id' => isset($plan->group_id) ? $plan->group_id : NULL,
'transfer_enable' => isset($plan->transfer_enable) ? $plan->transfer_enable * 1073741824 : 0,
'expired_at' => $request->input('expired_at') ?? NULL,
'uuid' => Helper::guid(true),
'token' => Helper::guid()
];
if (User::where('email', $user['email'])->first()) {
abort(500, '邮箱已存在于系统中');
}
$user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT);
if (!User::create($user)) {
abort(500, '生成失败');
}
return response([
'data' => true
]);
}
if ($request->input('generate_count')) {
$this->multiGenerate($request);
}
}
private function multiGenerate(Request $request)
{
if ($request->input('plan_id')) {
$plan = Plan::find($request->input('plan_id'));
if (!$plan) {
abort(500, '订阅计划不存在');
}
}
$users = [];
for ($i = 0;$i < $request->input('generate_count');$i++) {
$user = [
'email' => Helper::randomChar(6) . '@' . $request->input('email_suffix'),
'plan_id' => isset($plan->id) ? $plan->id : NULL,
'group_id' => isset($plan->group_id) ? $plan->group_id : NULL,
'transfer_enable' => isset($plan->transfer_enable) ? $plan->transfer_enable * 1073741824 : 0,
'expired_at' => $request->input('expired_at') ?? NULL,
'uuid' => Helper::guid(true),
'token' => Helper::guid(),
'created_at' => time(),
'updated_at' => time()
];
$user['password'] = password_hash($request->input('password') ?? $user['email'], PASSWORD_DEFAULT);
array_push($users, $user);
}
DB::beginTransaction();
if (!User::insert($users)) {
DB::rollBack();
abort(500, '生成失败');
}
DB::commit();
$data = "账号,密码,过期时间,UUID,创建时间,订阅地址\r\n";
foreach($users as $user) {
$expireDate = $user['expired_at'] === NULL ? '长期有效' : date('Y-m-d H:i:s', $user['expired_at']);
$createDate = date('Y-m-d H:i:s', $user['created_at']);
$password = $request->input('password') ?? $user['email'];
$subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']);
$data .= "{$user['email']},{$password},{$expireDate},{$user['uuid']},{$createDate},{$subscribeUrl}\r\n";
}
echo $data;
}
public function sendMail(UserSendMail $request)
{
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = User::orderBy($sort, $sortType);
$this->filter($request, $builder);
$users = $builder->get();
foreach ($users as $user) {
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => $request->input('subject'),
'template_name' => 'notify',
'template_value' => [
'name' => admin_setting('app_name', 'XBoard'),
'url' => admin_setting('app_url'),
'content' => $request->input('content')
]
],
'send_email_mass');
}
return response([
'data' => true
]);
}
public function ban(Request $request)
{
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = User::orderBy($sort, $sortType);
$this->filter($request, $builder);
try {
$builder->update([
'banned' => 1
]);
} catch (\Exception $e) {
abort(500, '处理失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace App\Http\Controllers\V1\Client;
use App\Http\Controllers\Controller;
use App\Services\ServerService;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\File;
use Symfony\Component\Yaml\Yaml;
class AppController extends Controller
{
public function getConfig(Request $request)
{
$servers = [];
$user = $request->user;
$userService = new UserService();
if ($userService->isAvailable($user)) {
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
}
$defaultConfig = base_path() . '/resources/rules/app.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.app.clash.yaml';
if (File::exists($customConfig)) {
$config = Yaml::parseFile($customConfig);
} else {
$config = Yaml::parseFile($defaultConfig);
}
$proxy = [];
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
array_push($proxy, \App\Protocols\Clash::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'vmess') {
array_push($proxy, \App\Protocols\Clash::buildVmess($user['uuid'], $item));
array_push($proxies, $item['name']);
}
if ($item['type'] === 'trojan') {
array_push($proxy, \App\Http\Controllers\V1\Client\Protocols\Clash::buildTrojan($user['uuid'], $item));
array_push($proxies, $item['name']);
}
}
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
foreach ($config['proxy-groups'] as $k => $v) {
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
}
return(Yaml::dump($config));
}
public function getVersion(Request $request)
{
if (strpos($request->header('user-agent'), 'tidalab/4.0.0') !== false
|| strpos($request->header('user-agent'), 'tunnelab/4.0.0') !== false
) {
if (strpos($request->header('user-agent'), 'Win64') !== false) {
return response([
'data' => [
'version' => admin_setting('windows_version'),
'download_url' => admin_setting('windows_download_url')
]
]);
} else {
return response([
'data' => [
'version' => admin_setting('macos_version'),
'download_url' => admin_setting('macos_download_url')
]
]);
}
return;
}
return response([
'data' => [
'windows_version' => admin_setting('windows_version'),
'windows_download_url' => admin_setting('windows_download_url'),
'macos_version' => admin_setting('macos_version'),
'macos_download_url' => admin_setting('macos_download_url'),
'android_version' => admin_setting('android_version'),
'android_download_url' => admin_setting('android_download_url')
]
]);
}
}

View File

@@ -0,0 +1,193 @@
<?php
namespace App\Http\Controllers\V1\Client;
use App\Http\Controllers\Controller;
use App\Protocols\General;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ClientController extends Controller
{
public function subscribe(Request $request)
{
// 节点类型筛选
$allowedTypes = ['vmess', 'vless', 'trojan', 'hysteria', 'hysteria2', 'shadowsocks'];
$types = $request->input('types', "vmess|vless|trojan|hysteria|shadowsocks");
$typesArr = $types ? collect(explode('|', str_replace(['|','',','], "|" , $types)))->reject(function($type) use ($allowedTypes){
return !in_array($type, $allowedTypes);
})->values()->all() : [];
// 节点关键词筛选字段获取
$filterArr = (mb_strlen($request->input('filter')) > 20) ? null : explode("|" ,str_replace(['|','',','], "|" , $request->input('filter')));
$flag = $request->input('flag') ?? $request->header('User-Agent', '');
$ip = $request->input('ip') ?? $request->ip();
preg_match('/(?<=\/)(\d+\.\d+\.\d+)|(?<=\/)(\d+)/', $flag, $matches);
$version = $matches[0]??null;
$isNekoBox = (stripos($flag, 'NekoBox') !== false); //判断是否为Nekobox客户端
$isSingBox = (stripos($flag, 'sing-box') !== false); //判断是否为stash客户端
$isShadowsocket = (stripos($flag, 'Shadowrocket') !== false); //判断是否为shadowsocket客户端
$isStash = (strpos($flag, 'Stash') !== false);
if(config('app.debug')){
Log::channel('daily')->info($flag);
}
$flag = strtolower($flag);
$user = $request->user;
// account not expired and is not banned.
$userService = new UserService();
if ($userService->isAvailable($user)) {
// 获取IP地址信息
$ip2region = new \Ip2Region();
$geo = filter_var($ip,FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? $ip2region->memorySearch($ip) : [];
$region = $geo['region'] ?? null;
// 获取服务器列表
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
// 判断不满足,不满足的直接过滤掉
$serversFiltered = collect($servers)->reject(function ($server) use ($typesArr, $filterArr, $region, $isNekoBox, $isStash, $version, $isSingBox, $isShadowsocket){
// 过滤类型
if($typesArr){
// 默认过滤掉hysteria2 线路
if($server['type'] == "hysteria" && $server['version'] == 2 && !in_array('hysteria2', $typesArr)
&& !($isNekoBox && $this->versionCompare($version, '1.2.7')) //1.2.7<=版本 自动下发hy2
&& !($isSingBox && $this->versionCompare($version, '1.5.0')) //1.5.0<=版本 自动下发hy2
&& !($isStash && $this->versionCompare($version, '2.5.0' )) //2.5.0或者以上版本自动下发hy2
&& !($isShadowsocket && $this->versionCompare($version, 1993)) //1993 版本或者以上的shadowsocket下发hy2
){
return true;
}
if(!in_array($server['type'], $typesArr) && !($server['type'] == "hysteria" && $server['version'] == 2 && in_array('hysteria2', $typesArr))) return true;
}
// 过滤关键词
if($filterArr){
$rejectFlag = true;
foreach($filterArr as $filter){
if(strpos($server['name'],$filter) !== false) $rejectFlag = false;
}
if($rejectFlag) return true;
}
// 过滤地区
if(strpos($region, '中国') !== false){
$excludes = $server['excludes'];
if(blank($excludes)) return false;
foreach($excludes as $v){
$excludeList = explode("|",str_replace(["",","," ",""],"|",$v));
$rejectFlag = false;
foreach($excludeList as $needle){
if(strpos($region, $needle) !== false){
return true;
}
}
};
}
})->values()->all();
$this->setSubscribeInfoToServers($serversFiltered, $user, count($servers) - count($serversFiltered));
$servers = $serversFiltered;
// 线路名称增加协议类型
if (admin_setting('show_protocol_to_server_enable')){
$typePrefixes = [
'hysteria' => [1 => '[Hy]', 2 => '[Hy2]'],
'vless' => '[vless]',
'shadowsocks' => '[ss]',
'vmess' => '[vmess]',
'trojan' => '[trojan]',
];
$servers = collect($servers)->map(function($server)use ($typePrefixes){
if (isset($typePrefixes[$server['type']])) {
// 如果是 hysteria 类型,根据版本选择前缀
$prefix = is_array($typePrefixes[$server['type']]) ? $typePrefixes[$server['type']][$server['version']] : $typePrefixes[$server['type']];
// 设置服务器名称
$server['name'] = $prefix . $server['name'];
}
return $server;
})->toArray();
}
if ($flag) {
foreach (array_reverse(glob(app_path('Protocols') . '/*.php')) as $file) {
$file = 'App\\Protocols\\' . basename($file, '.php');
$class = new $file($user, $servers);
$classFlags = explode(',', $class->flag);
$isMatch = function() use ($classFlags, $flag){
foreach ($classFlags as $classFlag){
if(strpos($flag, $classFlag) !== false) return true;
}
return false;
};
// 判断是否匹配
if ($isMatch()) {
return $class->handle();
}
}
}
$class = new General($user, $servers);
return $class->handle();
}
}
private function setSubscribeInfoToServers(&$servers, $user, $rejectServerCount = 0)
{
if (!isset($servers[0])) return;
if($rejectServerCount > 0){
array_unshift($servers, array_merge($servers[0], [
'name' => "去除{$rejectServerCount}条不合适线路",
]));
}
if (!(int)admin_setting('show_info_to_server_enable', 0)) return;
$useTraffic = $user['u'] + $user['d'];
$totalTraffic = $user['transfer_enable'];
$remainingTraffic = Helper::trafficConvert($totalTraffic - $useTraffic);
$expiredDate = $user['expired_at'] ? date('Y-m-d', $user['expired_at']) : '长期有效';
$userService = new UserService();
$resetDay = $userService->getResetDay($user);
// 筛选提示
array_unshift($servers, array_merge($servers[0], [
'name' => "套餐到期:{$expiredDate}",
]));
if ($resetDay) {
array_unshift($servers, array_merge($servers[0], [
'name' => "距离下次重置剩余:{$resetDay}",
]));
}
array_unshift($servers, array_merge($servers[0], [
'name' => "剩余流量:{$remainingTraffic}",
]));
}
/**
* 判断版本号
*/
function versionCompare($version1, $version2) {
if (!preg_match('/^\d+\.\d+\.\d+$|\d/', $version1) || !preg_match('/^\d+\.\d+\.\d+$|\d/', $version2)) {
return false;
}
$v1Parts = explode('.', $version1);
$v2Parts = explode('.', $version2);
$maxParts = max(count($v1Parts), count($v2Parts));
for ($i = 0; $i < $maxParts; $i++) {
$part1 = isset($v1Parts[$i]) ? (int)$v1Parts[$i] : 0;
$part2 = isset($v2Parts[$i]) ? (int)$v2Parts[$i] : 0;
if ($part1 < $part2) {
return false;
} elseif ($part1 > $part2) {
return true;
}
}
// 版本号相等
return true;
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\V1\Guest;
use App\Http\Controllers\Controller;
use App\Utils\Dict;
use Illuminate\Support\Facades\Http;
class CommController extends Controller
{
public function config()
{
return response([
'data' => [
'tos_url' => admin_setting('tos_url'),
'is_email_verify' => (int)admin_setting('email_verify', 0) ? 1 : 0,
'is_invite_force' => (int)admin_setting('invite_force', 0) ? 1 : 0,
'email_whitelist_suffix' => (int)admin_setting('email_whitelist_enable', 0)
? $this->getEmailSuffix()
: 0,
'is_recaptcha' => (int)admin_setting('recaptcha_enable', 0) ? 1 : 0,
'recaptcha_site_key' => admin_setting('recaptcha_site_key'),
'app_description' => admin_setting('app_description'),
'app_url' => admin_setting('app_url'),
'logo' => admin_setting('logo'),
]
]);
}
private function getEmailSuffix()
{
$suffix = admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT);
if (!is_array($suffix)) {
return preg_split('/,/', $suffix);
}
return $suffix;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Http\Controllers\V1\Guest;
use App\Http\Controllers\Controller;
use App\Models\Order;
use App\Services\OrderService;
use App\Services\PaymentService;
use App\Services\TelegramService;
use Illuminate\Http\Request;
class PaymentController extends Controller
{
public function notify($method, $uuid, Request $request)
{
try {
$paymentService = new PaymentService($method, null, $uuid);
$verify = $paymentService->notify($request->input());
if (!$verify) abort(500, 'verify error');
if (!$this->handle($verify['trade_no'], $verify['callback_no'])) {
abort(500, 'handle error');
}
return(isset($verify['custom_result']) ? $verify['custom_result'] : 'success');
} catch (\Exception $e) {
abort(500, 'fail');
}
}
private function handle($tradeNo, $callbackNo)
{
$order = Order::where('trade_no', $tradeNo)->first();
if (!$order) {
abort(500, 'order is not found');
}
if ($order->status !== 0) return true;
$orderService = new OrderService($order);
if (!$orderService->paid($callbackNo)) {
return false;
}
$telegramService = new TelegramService();
$message = sprintf(
"💰成功收款%s元\n———————————————\n订单号:%s",
$order->total_amount / 100,
$order->trade_no
);
$telegramService->sendMessageWithAdmin($message);
return true;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Http\Controllers\V1\Guest;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use Illuminate\Http\Request;
class PlanController extends Controller
{
public function fetch(Request $request)
{
$plan = Plan::where('show', 1)->get();
return response([
'data' => $plan
]);
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace App\Http\Controllers\V1\Guest;
use App\Http\Controllers\Controller;
use App\Services\TelegramService;
use Illuminate\Http\Request;
class TelegramController extends Controller
{
protected $msg;
protected $commands = [];
protected $telegramService;
public function __construct(Request $request)
{
if ($request->input('access_token') !== md5(admin_setting('telegram_bot_token'))) {
abort(401);
}
$this->telegramService = new TelegramService();
}
public function webhook(Request $request)
{
$this->formatMessage($request->input());
$this->formatChatJoinRequest($request->input());
$this->handle();
}
public function handle()
{
if (!$this->msg) return;
$msg = $this->msg;
$commandName = explode('@', $msg->command);
// To reduce request, only commands contains @ will get the bot name
if (count($commandName) == 2) {
$botName = $this->getBotName();
if ($commandName[1] === $botName){
$msg->command = $commandName[0];
}
}
try {
foreach (glob(base_path('app//Plugins//Telegram//Commands') . '/*.php') as $file) {
$command = basename($file, '.php');
$class = '\\App\\Plugins\\Telegram\\Commands\\' . $command;
if (!class_exists($class)) continue;
$instance = new $class();
if ($msg->message_type === 'message') {
if (!isset($instance->command)) continue;
if ($msg->command !== $instance->command) continue;
$instance->handle($msg);
return;
}
if ($msg->message_type === 'reply_message') {
if (!isset($instance->regex)) continue;
if (!preg_match($instance->regex, $msg->reply_text, $match)) continue;
$instance->handle($msg, $match);
return;
}
}
} catch (\Exception $e) {
$this->telegramService->sendMessage($msg->chat_id, $e->getMessage());
}
}
public function getBotName()
{
$response = $this->telegramService->getMe();
return $response->result->username;
}
private function formatMessage(array $data)
{
if (!isset($data['message'])) return;
if (!isset($data['message']['text'])) return;
$obj = new \StdClass();
$text = explode(' ', $data['message']['text']);
$obj->command = $text[0];
$obj->args = array_slice($text, 1);
$obj->chat_id = $data['message']['chat']['id'];
$obj->message_id = $data['message']['message_id'];
$obj->message_type = 'message';
$obj->text = $data['message']['text'];
$obj->is_private = $data['message']['chat']['type'] === 'private';
if (isset($data['message']['reply_to_message']['text'])) {
$obj->message_type = 'reply_message';
$obj->reply_text = $data['message']['reply_to_message']['text'];
}
$this->msg = $obj;
}
private function formatChatJoinRequest(array $data)
{
if (!isset($data['chat_join_request'])) return;
if (!isset($data['chat_join_request']['from']['id'])) return;
if (!isset($data['chat_join_request']['chat']['id'])) return;
$user = \App\Models\User::where('telegram_id', $data['chat_join_request']['from']['id'])
->first();
if (!$user) {
$this->telegramService->declineChatJoinRequest(
$data['chat_join_request']['chat']['id'],
$data['chat_join_request']['from']['id']
);
return;
}
$userService = new \App\Services\UserService();
if (!$userService->isAvailable($user)) {
$this->telegramService->declineChatJoinRequest(
$data['chat_join_request']['chat']['id'],
$data['chat_join_request']['from']['id']
);
return;
}
$userService = new \App\Services\UserService();
$this->telegramService->approveChatJoinRequest(
$data['chat_join_request']['chat']['id'],
$data['chat_join_request']['from']['id']
);
}
}

View File

@@ -0,0 +1,311 @@
<?php
namespace App\Http\Controllers\V1\Passport;
use App\Http\Controllers\Controller;
use App\Http\Requests\Passport\AuthForget;
use App\Http\Requests\Passport\AuthLogin;
use App\Http\Requests\Passport\AuthRegister;
use App\Jobs\SendEmailJob;
use App\Models\InviteCode;
use App\Models\Plan;
use App\Models\User;
use App\Services\AuthService;
use App\Utils\CacheKey;
use App\Utils\Dict;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use ReCaptcha\ReCaptcha;
class AuthController extends Controller
{
public function loginWithMailLink(Request $request)
{
if (!(int)admin_setting('login_with_mail_link_enable')) {
abort(404);
}
$params = $request->validate([
'email' => 'required|email:strict',
'redirect' => 'nullable'
]);
if (Cache::get(CacheKey::get('LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP', $params['email']))) {
abort(500, __('Sending frequently, please try again later'));
}
$user = User::where('email', $params['email'])->first();
if (!$user) {
return response([
'data' => true
]);
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 300);
Cache::put(CacheKey::get('LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP', $params['email']), time(), 60);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (admin_setting('app_url')) {
$link = admin_setting('app_url') . $redirect;
} else {
$link = url($redirect);
}
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => __('Login to :name', [
'name' => admin_setting('app_name', 'XBoard')
]),
'template_name' => 'login',
'template_value' => [
'name' => admin_setting('app_name', 'XBoard'),
'link' => $link,
'url' => admin_setting('app_url')
]
]);
return response([
'data' => $link
]);
}
public function register(AuthRegister $request)
{
if ((int)admin_setting('register_limit_by_ip_enable', 0)) {
$registerCountByIP = Cache::get(CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip())) ?? 0;
if ((int)$registerCountByIP >= (int)admin_setting('register_limit_count', 3)) {
abort(500, __('Register frequently, please try again after :minute minute', [
'minute' => admin_setting('register_limit_expire', 60)
]));
}
}
if ((int)admin_setting('recaptcha_enable', 0)) {
$recaptcha = new ReCaptcha(admin_setting('recaptcha_key'));
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
if (!$recaptchaResp->isSuccess()) {
abort(500, __('Invalid code is incorrect'));
}
}
if ((int)admin_setting('email_whitelist_enable', 0)) {
if (!Helper::emailSuffixVerify(
$request->input('email'),
admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT))
) {
abort(500, __('Email suffix is not in the Whitelist'));
}
}
if ((int)admin_setting('email_gmail_limit_enable', 0)) {
$prefix = explode('@', $request->input('email'))[0];
if (strpos($prefix, '.') !== false || strpos($prefix, '+') !== false) {
abort(500, __('Gmail alias is not supported'));
}
}
if ((int)admin_setting('stop_register', 0)) {
abort(500, __('Registration has closed'));
}
if ((int)admin_setting('invite_force', 0)) {
if (empty($request->input('invite_code'))) {
abort(500, __('You must use the invitation code to register'));
}
}
if ((int)admin_setting('email_verify', 0)) {
if (empty($request->input('email_code'))) {
abort(500, __('Email verification code cannot be empty'));
}
if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) {
abort(500, __('Incorrect email verification code'));
}
}
$email = $request->input('email');
$password = $request->input('password');
$exist = User::where('email', $email)->first();
if ($exist) {
abort(500, __('Email already exists'));
}
$user = new User();
$user->email = $email;
$user->password = password_hash($password, PASSWORD_DEFAULT);
$user->uuid = Helper::guid(true);
$user->token = Helper::guid();
if ($request->input('invite_code')) {
$inviteCode = InviteCode::where('code', $request->input('invite_code'))
->where('status', 0)
->first();
if (!$inviteCode) {
if ((int)admin_setting('invite_force', 0)) {
abort(500, __('Invalid invitation code'));
}
} else {
$user->invite_user_id = $inviteCode->user_id ? $inviteCode->user_id : null;
if (!(int)admin_setting('invite_never_expire', 0)) {
$inviteCode->status = 1;
$inviteCode->save();
}
}
}
// try out
if ((int)admin_setting('try_out_plan_id', 0)) {
$plan = Plan::find(admin_setting('try_out_plan_id'));
if ($plan) {
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = time() + (admin_setting('try_out_hour', 1) * 3600);
$user->speed_limit = $plan->speed_limit;
}
}
if (!$user->save()) {
abort(500, __('Register failed'));
}
if ((int)admin_setting('email_verify', 0)) {
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
}
$user->last_login_at = time();
$user->save();
if ((int)admin_setting('register_limit_by_ip_enable', 0)) {
Cache::put(
CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip()),
(int)$registerCountByIP + 1,
(int)admin_setting('register_limit_expire', 60) * 60
);
}
$authService = new AuthService($user);
return response()->json([
'data' => $authService->generateAuthData($request)
]);
}
public function login(AuthLogin $request)
{
$email = $request->input('email');
$password = $request->input('password');
if ((int)admin_setting('password_limit_enable', 1)) {
$passwordErrorCount = (int)Cache::get(CacheKey::get('PASSWORD_ERROR_LIMIT', $email), 0);
if ($passwordErrorCount >= (int)admin_setting('password_limit_count', 5)) {
abort(500, __('There are too many password errors, please try again after :minute minutes.', [
'minute' => admin_setting('password_limit_expire', 60)
]));
}
}
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, __('Incorrect email or password'));
}
if (!Helper::multiPasswordVerify(
$user->password_algo,
$user->password_salt,
$password,
$user->password)
) {
if ((int)admin_setting('password_limit_enable')) {
Cache::put(
CacheKey::get('PASSWORD_ERROR_LIMIT', $email),
(int)$passwordErrorCount + 1,
60 * (int)admin_setting('password_limit_expire', 60)
);
}
abort(500, __('Incorrect email or password'));
}
if ($user->banned) {
abort(500, __('Your account has been suspended'));
}
$authService = new AuthService($user);
return response([
'data' => $authService->generateAuthData($request)
]);
}
public function token2Login(Request $request)
{
if ($request->input('token')) {
$redirect = '/#/login?verify=' . $request->input('token') . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (admin_setting('app_url')) {
$location = admin_setting('app_url') . $redirect;
} else {
$location = url($redirect);
}
return redirect()->to($location)->send();
}
if ($request->input('verify')) {
$key = CacheKey::get('TEMP_TOKEN', $request->input('verify'));
$userId = Cache::get($key);
if (!$userId) {
abort(500, __('Token error'));
}
$user = User::find($userId);
if (!$user) {
abort(500, __('The user does not '));
}
if ($user->banned) {
abort(500, __('Your account has been suspended'));
}
Cache::forget($key);
$authService = new AuthService($user);
return response([
'data' => $authService->generateAuthData($request)
]);
}
}
public function getQuickLoginUrl(Request $request)
{
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if (!$authorization) abort(403, '未登录或登陆已过期');
$user = AuthService::decryptAuthData($authorization);
if (!$user) abort(403, '未登录或登陆已过期');
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user['id'], 60);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (admin_setting('app_url')) {
$url = admin_setting('app_url') . $redirect;
} else {
$url = url($redirect);
}
return response([
'data' => $url
]);
}
public function forget(AuthForget $request)
{
$forgetRequestLimitKey = CacheKey::get('FORGET_REQUEST_LIMIT', $request->input('email'));
$forgetRequestLimit = (int)Cache::get($forgetRequestLimitKey);
if ($forgetRequestLimit >= 3) abort(500, __('Reset failed, Please try again later'));
if ((string)Cache::get(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email'))) !== (string)$request->input('email_code')) {
Cache::put($forgetRequestLimitKey, $forgetRequestLimit ? $forgetRequestLimit + 1 : 1, 300);
abort(500, __('Incorrect email verification code'));
}
$user = User::where('email', $request->input('email'))->first();
if (!$user) {
abort(500, __('This email is not registered in the system'));
}
$user->password = password_hash($request->input('password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
$user->password_salt = NULL;
if (!$user->save()) {
abort(500, __('Reset failed'));
}
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Http\Controllers\V1\Passport;
use App\Http\Controllers\Controller;
use App\Http\Requests\Passport\CommSendEmailVerify;
use App\Jobs\SendEmailJob;
use App\Models\InviteCode;
use App\Models\User;
use App\Utils\CacheKey;
use App\Utils\Dict;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Mail;
use ReCaptcha\ReCaptcha;
class CommController extends Controller
{
private function isEmailVerify()
{
return response([
'data' => (int)admin_setting('email_verify', 0) ? 1 : 0
]);
}
public function sendEmailVerify(CommSendEmailVerify $request)
{
if ((int)admin_setting('recaptcha_enable', 0)) {
$recaptcha = new ReCaptcha(admin_setting('recaptcha_key'));
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
if (!$recaptchaResp->isSuccess()) {
abort(500, __('Invalid code is incorrect'));
}
}
$email = $request->input('email');
if (Cache::get(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email))) {
abort(500, __('Email verification code has been sent, please request again later'));
}
$code = rand(100000, 999999);
$subject = admin_setting('app_name', 'XBoard') . __('Email verification code');
SendEmailJob::dispatch([
'email' => $email,
'subject' => $subject,
'template_name' => 'verify',
'template_value' => [
'name' => admin_setting('app_name', 'XBoard'),
'code' => $code,
'url' => admin_setting('app_url')
]
]);
Cache::put(CacheKey::get('EMAIL_VERIFY_CODE', $email), $code, 300);
Cache::put(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email), time(), 60);
return response([
'data' => true
]);
}
public function pv(Request $request)
{
$inviteCode = InviteCode::where('code', $request->input('invite_code'))->first();
if ($inviteCode) {
$inviteCode->pv = $inviteCode->pv + 1;
$inviteCode->save();
}
return response([
'data' => true
]);
}
private function getEmailSuffix()
{
$suffix = admin_setting('email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT);
if (!is_array($suffix)) {
return preg_split('/,/', $suffix);
}
return $suffix;
}
}

View File

@@ -0,0 +1,231 @@
<?php
namespace App\Http\Controllers\V1\Server;
use App\Http\Controllers\Controller;
use App\Models\ServerVmess;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/*
* V2ray Aurora
* Github: https://github.com/tokumeikoi/aurora
*/
class DeepbworkController extends Controller
{
CONST V2RAY_CONFIG = '{"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"api":{"services":["HandlerService","StatsService"],"tag":"api"},"dns":{},"stats":{},"inbounds":[{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled":true,"destOverride":["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},{"listen":"127.0.0.1","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"outbounds":[{"protocol":"freedom","settings":{}},{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"type":"field","inboundTag":"api","outboundTag":"api"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
public function __construct(Request $request)
{
$token = $request->input('token');
if (empty($token)) {
abort(500, 'token is null');
}
if ($token !== admin_setting('server_token')) {
abort(500, 'token is error');
}
}
// 后端获取用户
public function user(Request $request)
{
ini_set('memory_limit', -1);
$nodeId = $request->input('node_id');
$server = ServerVmess::find($nodeId);
if (!$server) {
abort(500, 'fail');
}
Cache::put(CacheKey::get('SERVER_VMESS_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers($server->group_id);
$result = [];
foreach ($users as $user) {
$user->v2ray_user = [
"uuid" => $user->uuid,
"email" => sprintf("%s@v2board.user", $user->uuid),
"alter_id" => 0,
"level" => 0,
];
unset($user['uuid']);
array_push($result, $user);
}
$eTag = sha1(json_encode($result));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
return response(null,304);
}
return response([
'msg' => 'ok',
'data' => $result,
])->header('ETag', "\"{$eTag}\"");
}
// 后端提交数据
public function submit(Request $request)
{
$server = ServerVmess::find($request->input('node_id'));
if (!$server) {
return response([
'ret' => 0,
'msg' => 'server is not found'
]);
}
$data = get_request_content();
$data = json_decode($data, true);
Cache::put(CacheKey::get('SERVER_VMESS_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_VMESS_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
$formatData = [];
foreach ($data as $item) {
$formatData[$item['user_id']] = [$item['u'], $item['d']];
}
$userService->trafficFetch($server->toArray(), 'vmess', $formatData);
return response([
'ret' => 1,
'msg' => 'ok'
]);
}
// 后端获取配置
public function config(Request $request)
{
$nodeId = $request->input('node_id');
$localPort = $request->input('local_port');
if (empty($nodeId) || empty($localPort)) {
abort(500, '参数错误');
}
try {
$json = $this->getV2RayConfig($nodeId, $localPort);
} catch (\Exception $e) {
abort(500, $e->getMessage());
}
return(json_encode($json, JSON_UNESCAPED_UNICODE));
}
private function getV2RayConfig(int $nodeId, int $localPort)
{
$server = ServerVmess::find($nodeId);
if (!$server) {
abort(500, '节点不存在');
}
$json = json_decode(self::V2RAY_CONFIG);
$json->log->loglevel = (int)admin_setting('server_log_enable') ? 'debug' : 'none';
$json->inbounds[1]->port = (int)$localPort;
$json->inbounds[0]->port = (int)$server->server_port;
$json->inbounds[0]->streamSettings->network = $server->network;
$this->setDns($server, $json);
$this->setNetwork($server, $json);
$this->setRule($server, $json);
$this->setTls($server, $json);
return $json;
}
private function setDns(ServerVmess $server, object $json)
{
if ($server->dnsSettings) {
$dns = $server->dnsSettings;
if (isset($dns->servers)) {
array_push($dns->servers, '1.1.1.1');
array_push($dns->servers, 'localhost');
}
$json->dns = $dns;
$json->outbounds[0]->settings->domainStrategy = 'UseIP';
}
}
private function setNetwork(ServerVmess $server, object $json)
{
if ($server->networkSettings) {
switch ($server->network) {
case 'tcp':
$json->inbounds[0]->streamSettings->tcpSettings = $server->networkSettings;
break;
case 'kcp':
$json->inbounds[0]->streamSettings->kcpSettings = $server->networkSettings;
break;
case 'ws':
$json->inbounds[0]->streamSettings->wsSettings = $server->networkSettings;
break;
case 'http':
$json->inbounds[0]->streamSettings->httpSettings = $server->networkSettings;
break;
case 'domainsocket':
$json->inbounds[0]->streamSettings->dsSettings = $server->networkSettings;
break;
case 'quic':
$json->inbounds[0]->streamSettings->quicSettings = $server->networkSettings;
break;
case 'grpc':
$json->inbounds[0]->streamSettings->grpcSettings = $server->networkSettings;
break;
}
}
}
private function setRule(ServerVmess $server, object $json)
{
$domainRules = array_filter(explode(PHP_EOL, admin_setting('server_v2ray_domain')));
$protocolRules = array_filter(explode(PHP_EOL, admin_setting('server_v2ray_protocol')));
if ($server->ruleSettings) {
$ruleSettings = $server->ruleSettings;
// domain
if (isset($ruleSettings->domain)) {
$ruleSettings->domain = array_filter($ruleSettings->domain);
if (!empty($ruleSettings->domain)) {
$domainRules = array_merge($domainRules, $ruleSettings->domain);
}
}
// protocol
if (isset($ruleSettings->protocol)) {
$ruleSettings->protocol = array_filter($ruleSettings->protocol);
if (!empty($ruleSettings->protocol)) {
$protocolRules = array_merge($protocolRules, $ruleSettings->protocol);
}
}
}
if (!empty($domainRules)) {
$domainObj = new \StdClass();
$domainObj->type = 'field';
$domainObj->domain = $domainRules;
$domainObj->outboundTag = 'block';
array_push($json->routing->rules, $domainObj);
}
if (!empty($protocolRules)) {
$protocolObj = new \StdClass();
$protocolObj->type = 'field';
$protocolObj->protocol = $protocolRules;
$protocolObj->outboundTag = 'block';
array_push($json->routing->rules, $protocolObj);
}
if (empty($domainRules) && empty($protocolRules)) {
$json->inbounds[0]->sniffing->enabled = false;
}
}
private function setTls(ServerVMess $server, object $json)
{
if ((int)$server->tls) {
$tlsSettings = $server->tlsSettings;
$json->inbounds[0]->streamSettings->security = 'tls';
$tls = (object)[
'certificateFile' => '/root/.cert/server.crt',
'keyFile' => '/root/.cert/server.key'
];
$json->inbounds[0]->streamSettings->tlsSettings = new \StdClass();
if (isset($tlsSettings->serverName)) {
$json->inbounds[0]->streamSettings->tlsSettings->serverName = (string)$tlsSettings->serverName;
}
if (isset($tlsSettings->allowInsecure)) {
$json->inbounds[0]->streamSettings->tlsSettings->allowInsecure = (int)$tlsSettings->allowInsecure ? true : false;
}
$json->inbounds[0]->streamSettings->tlsSettings->certificates[0] = $tls;
}
}
}

View File

@@ -0,0 +1,87 @@
<?php
namespace App\Http\Controllers\V1\Server;
use App\Http\Controllers\Controller;
use App\Models\ServerShadowsocks;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
/*
* Tidal Lab Shadowsocks
* Github: https://github.com/tokumeikoi/tidalab-ss
*/
class ShadowsocksTidalabController extends Controller
{
public function __construct(Request $request)
{
$token = $request->input('token');
if (empty($token)) {
abort(500, 'token is null');
}
if ($token !== admin_setting('server_token')) {
abort(500, 'token is error');
}
}
// 后端获取用户
public function user(Request $request)
{
ini_set('memory_limit', -1);
$nodeId = $request->input('node_id');
$server = ServerShadowsocks::find($nodeId);
if (!$server) {
abort(500, 'fail');
}
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers($server->group_id);
$result = [];
foreach ($users as $user) {
array_push($result, [
'id' => $user->id,
'port' => $server->server_port,
'cipher' => $server->cipher,
'secret' => $user->uuid
]);
}
$eTag = sha1(json_encode($result));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
return response(null,304);
}
return response([
'data' => $result
])->header('ETag', "\"{$eTag}\"");
}
// 后端提交数据
public function submit(Request $request)
{
$server = ServerShadowsocks::find($request->input('node_id'));
if (!$server) {
return response([
'ret' => 0,
'msg' => 'server is not found'
]);
}
$data = get_request_content();
$data = json_decode($data, true);
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
$formatData = [];
foreach ($data as $item) {
$formatData[$item['user_id']] = [$item['u'], $item['d']];
}
$userService->trafficFetch($server->toArray(), 'shadowsocks', $formatData);
return response([
'ret' => 1,
'msg' => 'ok'
]);
}
}

View File

@@ -0,0 +1,122 @@
<?php
namespace App\Http\Controllers\V1\Server;
use App\Http\Controllers\Controller;
use App\Models\ServerTrojan;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
/*
* Tidal Lab Trojan
* Github: https://github.com/tokumeikoi/tidalab-trojan
*/
class TrojanTidalabController extends Controller
{
CONST TROJAN_CONFIG = '{"run_type":"server","local_addr":"0.0.0.0","local_port":443,"remote_addr":"www.taobao.com","remote_port":80,"password":[],"ssl":{"cert":"server.crt","key":"server.key","sni":"domain.com"},"api":{"enabled":true,"api_addr":"127.0.0.1","api_port":10000}}';
public function __construct(Request $request)
{
$token = $request->input('token');
if (empty($token)) {
abort(500, 'token is null');
}
if ($token !== admin_setting('server_token')) {
abort(500, 'token is error');
}
}
// 后端获取用户
public function user(Request $request)
{
ini_set('memory_limit', -1);
$nodeId = $request->input('node_id');
$server = ServerTrojan::find($nodeId);
if (!$server) {
abort(500, 'fail');
}
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers($server->group_id);
$result = [];
foreach ($users as $user) {
$user->trojan_user = [
"password" => $user->uuid,
];
unset($user['uuid']);
array_push($result, $user);
}
$eTag = sha1(json_encode($result));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
return response(null,304);
}
return response([
'msg' => 'ok',
'data' => $result,
])->header('ETag', "\"{$eTag}\"");
}
// 后端提交数据
public function submit(Request $request)
{
$server = ServerTrojan::find($request->input('node_id'));
if (!$server) {
return response([
'ret' => 0,
'msg' => 'server is not found'
]);
}
$data = get_request_content();
$data = json_decode($data, true);
Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_PUSH_AT', $server->id), time(), 3600);
$userService = new UserService();
$formatData = [];
foreach ($data as $item) {
$formatData[$item['user_id']] = [$item['u'], $item['d']];
}
$userService->trafficFetch($server->toArray(), 'trojan', $formatData);
return response([
'ret' => 1,
'msg' => 'ok'
]);
}
// 后端获取配置
public function config(Request $request)
{
$nodeId = $request->input('node_id');
$localPort = $request->input('local_port');
if (empty($nodeId) || empty($localPort)) {
abort(500, '参数错误');
}
try {
$json = $this->getTrojanConfig($nodeId, $localPort);
} catch (\Exception $e) {
abort(500, $e->getMessage());
}
return(json_encode($json, JSON_UNESCAPED_UNICODE));
}
private function getTrojanConfig(int $nodeId, int $localPort)
{
$server = ServerTrojan::find($nodeId);
if (!$server) {
abort(500, '节点不存在');
}
$json = json_decode(self::TROJAN_CONFIG);
$json->local_port = $server->server_port;
$json->ssl->sni = $server->server_name ? $server->server_name : $server->host;
$json->ssl->cert = "/root/.cert/server.crt";
$json->ssl->key = "/root/.cert/server.key";
$json->api->api_port = $localPort;
return $json;
}
}

View File

@@ -0,0 +1,176 @@
<?php
namespace App\Http\Controllers\V1\Server;
use App\Http\Controllers\Controller;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class UniProxyController extends Controller
{
private $nodeType;
private $nodeInfo;
private $nodeId;
private $serverService;
public function __construct(ServerService $serverService, Request $request)
{
$this->serverService = $serverService;
$this->nodeId = $request->input('node_id');
$this->nodeType = $request->input('node_type') === 'v2ray' ? 'vmess' : $request->input('node_type');
$this->nodeInfo = $this->serverService->getServer($this->nodeId, $this->nodeType);
if(!$this->nodeInfo) {
Log::channel("daily")->info("$this->nodeId $this->nodeType $this->nodeInfo");
throw new \Exception('server is not exist', 500);
};
}
// 后端获取用户
public function user(Request $request)
{
ini_set('memory_limit', -1);
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_CHECK_AT', $this->nodeInfo->id), time(), 3600);
$users = $this->serverService->getAvailableUsers($this->nodeInfo->group_id);
$users = $users->toArray();
$response['users'] = $users;
$eTag = sha1(json_encode($response));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
return response(null, 304);
};
return response($response)->header('ETag', "\"{$eTag}\"");
}
// 后端提交数据
public function push(Request $request)
{
$data = get_request_content();
$data = json_decode($data, true);
// 增加单节点多服务器统计在线人数
$ip = $request->ip();
$id = $request->input("id");
$time = time();
$cacheKey = CacheKey::get('MULTI_SERVER_' . strtoupper($this->nodeType) . '_ONLINE_USER', $this->nodeInfo->id);
// 1、获取节点节点在线人数缓存
$onlineUsers = Cache::get($cacheKey) ?? [];
$onlineCollection = collect($onlineUsers);
// 过滤掉超过600秒的记录
$onlineCollection = $onlineCollection->reject(function ($item) use ($time) {
return $item['time'] < ($time - 600);
});
// 定义数据
$updatedItem = [
'id' => $id ?? $ip,
'ip' => $ip,
'online_user' => count($data),
'time' => $time
];
$existingItemIndex = $onlineCollection->search(function ($item) use ($updatedItem) {
return ($item['id'] ?? '') === $updatedItem['id'];
});
if ($existingItemIndex !== false) {
$onlineCollection[$existingItemIndex] = $updatedItem;
} else {
$onlineCollection->push($updatedItem);
}
$onlineUsers = $onlineCollection->all();
Cache::put($cacheKey, $onlineUsers, 3600);
$online_user = $onlineCollection->sum('online_user');
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_ONLINE_USER', $this->nodeInfo->id), $online_user, 3600);
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_PUSH_AT', $this->nodeInfo->id), time(), 3600);
// 查询是否存在子节点
$childServer = null;
if ($this->nodeInfo->parent_id == null) $childServer = $this->serverService->getChildServer($this->nodeId, $this->nodeType, $ip);
$userService = new UserService();
$userService->trafficFetch($this->nodeInfo->toArray(), $this->nodeType, $data, $childServer ? $childServer->toArray() : null);
return response([
'data' => true
]);
}
// 后端获取配置
public function config(Request $request)
{
switch ($this->nodeType) {
case 'shadowsocks':
$response = [
'server_port' => $this->nodeInfo->server_port,
'cipher' => $this->nodeInfo->cipher,
'obfs' => $this->nodeInfo->obfs,
'obfs_settings' => $this->nodeInfo->obfs_settings
];
if ($this->nodeInfo->cipher === '2022-blake3-aes-128-gcm') {
$response['server_key'] = Helper::getServerKey($this->nodeInfo->created_at, 16);
}
if ($this->nodeInfo->cipher === '2022-blake3-aes-256-gcm') {
$response['server_key'] = Helper::getServerKey($this->nodeInfo->created_at, 32);
}
break;
case 'vmess':
$response = [
'server_port' => $this->nodeInfo->server_port,
'network' => $this->nodeInfo->network,
'networkSettings' => $this->nodeInfo->networkSettings,
'tls' => $this->nodeInfo->tls
];
break;
case 'trojan':
$response = [
'host' => $this->nodeInfo->host,
'server_port' => $this->nodeInfo->server_port,
'server_name' => $this->nodeInfo->server_name,
'network' => $this->nodeInfo->network,
'networkSettings' => $this->nodeInfo->networkSettings,
];
break;
case 'hysteria':
$response = [
'version' => $this->nodeInfo->version,
'host' => $this->nodeInfo->host,
'server_port' => $this->nodeInfo->server_port,
'server_name' => $this->nodeInfo->server_name,
'up_mbps' => $this->nodeInfo->up_mbps,
'down_mbps' => $this->nodeInfo->down_mbps,
'obfs' => $this->nodeInfo->is_obfs ? Helper::getServerKey($this->nodeInfo->created_at, 16) : null
];
break;
case "vless":
$response = [
'server_port' => $this->nodeInfo->server_port,
'network' => $this->nodeInfo->network,
'network_settings' => $this->nodeInfo->network_settings,
'tls' => $this->nodeInfo->tls,
'flow' => $this->nodeInfo->flow,
'tls_settings' => $this->nodeInfo->tls_settings
];
break;
}
$response['base_config'] = [
'push_interval' => (int)admin_setting('server_push_interval', 60),
'pull_interval' => (int)admin_setting('server_pull_interval', 60)
];
if ($this->nodeInfo['route_id']) {
$response['routes'] = $this->serverService->getRoutes($this->nodeInfo['route_id']);
}
$eTag = sha1(json_encode($response));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
return response(null,304);
}
return response($response)->header('ETag', "\"{$eTag}\"");
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\V1\Staff;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\NoticeSave;
use App\Models\Notice;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class NoticeController extends Controller
{
public function fetch(Request $request)
{
return response([
'data' => Notice::orderBy('id', 'DESC')->get()
]);
}
public function save(NoticeSave $request)
{
$data = $request->only([
'title',
'content',
'img_url'
]);
if (!$request->input('id')) {
if (!Notice::create($data)) {
abort(500, '保存失败');
}
} else {
try {
Notice::find($request->input('id'))->update($data);
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$notice = Notice::find($request->input('id'));
if (!$notice) {
abort(500, '公告不存在');
}
if (!$notice->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers\V1\Staff;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class PlanController extends Controller
{
public function fetch(Request $request)
{
$counts = User::select(
DB::raw("plan_id"),
DB::raw("count(*) as count")
)
->where('plan_id', '!=', NULL)
->where(function ($query) {
$query->where('expired_at', '>=', time())
->orWhere('expired_at', NULL);
})
->groupBy("plan_id")
->get();
$plans = Plan::orderBy('sort', 'ASC')->get();
foreach ($plans as $k => $v) {
$plans[$k]->count = 0;
foreach ($counts as $kk => $vv) {
if ($plans[$k]->id === $counts[$kk]->plan_id) $plans[$k]->count = $counts[$kk]->count;
}
}
return response([
'data' => $plans
]);
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Http\Controllers\V1\Staff;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
use App\Models\TicketMessage;
use App\Services\TicketService;
use Illuminate\Http\Request;
class TicketController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id'))
->first();
if (!$ticket) {
abort(500, '工单不存在');
}
$ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get();
for ($i = 0; $i < count($ticket['message']); $i++) {
if ($ticket['message'][$i]['user_id'] !== $ticket->user_id) {
$ticket['message'][$i]['is_me'] = true;
} else {
$ticket['message'][$i]['is_me'] = false;
}
}
return response([
'data' => $ticket
]);
}
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$model = Ticket::orderBy('created_at', 'DESC');
if ($request->input('status') !== NULL) {
$model->where('status', $request->input('status'));
}
$total = $model->count();
$res = $model->forPage($current, $pageSize)
->get();
return response([
'data' => $res,
'total' => $total
]);
}
public function reply(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
if (empty($request->input('message'))) {
abort(500, '消息不能为空');
}
$ticketService = new TicketService();
$ticketService->replyByAdmin(
$request->input('id'),
$request->input('message'),
$request->user['id']
);
return response([
'data' => true
]);
}
public function close(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$ticket = Ticket::where('id', $request->input('id'))
->first();
if (!$ticket) {
abort(500, '工单不存在');
}
$ticket->status = 1;
if (!$ticket->save()) {
abort(500, '关闭失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace App\Http\Controllers\V1\Staff;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\UserSendMail;
use App\Http\Requests\Staff\UserUpdate;
use App\Jobs\SendEmailJob;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function getUserInfoById(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
$user = User::where('is_admin', 0)
->where('id', $request->input('id'))
->where('is_staff', 0)
->first();
if (!$user) abort(500, '用户不存在');
return response([
'data' => $user
]);
}
public function update(UserUpdate $request)
{
$params = $request->validated();
$user = User::find($request->input('id'));
if (!$user) {
abort(500, '用户不存在');
}
if (User::where('email', $params['email'])->first() && $user->email !== $params['email']) {
abort(500, '邮箱已被使用');
}
if (isset($params['password'])) {
$params['password'] = password_hash($params['password'], PASSWORD_DEFAULT);
$params['password_algo'] = NULL;
} else {
unset($params['password']);
}
if (isset($params['plan_id'])) {
$plan = Plan::find($params['plan_id']);
if (!$plan) {
abort(500, '订阅计划不存在');
}
$params['group_id'] = $plan->group_id;
}
try {
$user->update($params);
} catch (\Exception $e) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function sendMail(UserSendMail $request)
{
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = User::orderBy($sort, $sortType);
$this->filter($request, $builder);
$users = $builder->get();
foreach ($users as $user) {
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => $request->input('subject'),
'template_name' => 'notify',
'template_value' => [
'name' => admin_setting('app_name', 'XBoard'),
'url' => admin_setting('app_url'),
'content' => $request->input('content')
]
]);
}
return response([
'data' => true
]);
}
public function ban(Request $request)
{
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$builder = User::orderBy($sort, $sortType);
$this->filter($request, $builder);
try {
$builder->update([
'banned' => 1
]);
} catch (\Exception $e) {
abort(500, '处理失败');
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Models\Payment;
use App\Utils\Dict;
use Illuminate\Http\Request;
class CommController extends Controller
{
public function config()
{
return response([
'data' => [
'is_telegram' => (int)admin_setting('telegram_bot_enable', 0),
'telegram_discuss_link' => admin_setting('telegram_discuss_link'),
'stripe_pk' => admin_setting('stripe_pk_live'),
'withdraw_methods' => admin_setting('commission_withdraw_method', Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT),
'withdraw_close' => (int)admin_setting('withdraw_close_enable', 0),
'currency' => admin_setting('currency', 'CNY'),
'currency_symbol' => admin_setting('currency_symbol', '¥'),
'commission_distribution_enable' => (int)admin_setting('commission_distribution_enable', 0),
'commission_distribution_l1' => admin_setting('commission_distribution_l1'),
'commission_distribution_l2' => admin_setting('commission_distribution_l2'),
'commission_distribution_l3' => admin_setting('commission_distribution_l3')
]
]);
}
public function getStripePublicKey(Request $request)
{
$payment = Payment::where('id', $request->input('id'))
->where('payment', 'StripeCredit')
->first();
if (!$payment) abort(500, 'payment is not found');
return response([
'data' => $payment->config['stripe_pk_live']
]);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Services\CouponService;
use Illuminate\Http\Request;
class CouponController extends Controller
{
public function check(Request $request)
{
if (empty($request->input('code'))) {
abort(500, __('Coupon cannot be empty'));
}
$couponService = new CouponService($request->input('code'));
$couponService->setPlanId($request->input('plan_id'));
$couponService->setUserId($request->user['id']);
$couponService->check();
return response([
'data' => $couponService->getCoupon()
]);
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Models\CommissionLog;
use App\Models\InviteCode;
use App\Models\Order;
use App\Models\User;
use App\Utils\Helper;
use Illuminate\Http\Request;
class InviteController extends Controller
{
public function save(Request $request)
{
if (InviteCode::where('user_id', $request->user['id'])->where('status', 0)->count() >= admin_setting('invite_gen_limit', 5)) {
abort(500, __('The maximum number of creations has been reached'));
}
$inviteCode = new InviteCode();
$inviteCode->user_id = $request->user['id'];
$inviteCode->code = Helper::randomChar(8);
return response([
'data' => $inviteCode->save()
]);
}
public function details(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('page_size') >= 10 ? $request->input('page_size') : 10;
$builder = CommissionLog::where('invite_user_id', $request->user['id'])
->where('get_amount', '>', 0)
->select([
'id',
'trade_no',
'order_amount',
'get_amount',
'created_at'
])
->orderBy('created_at', 'DESC');
$total = $builder->count();
$details = $builder->forPage($current, $pageSize)
->get();
return response([
'data' => $details,
'total' => $total
]);
}
public function fetch(Request $request)
{
$codes = InviteCode::where('user_id', $request->user['id'])
->where('status', 0)
->get();
$commission_rate = admin_setting('invite_commission', 10);
$user = User::find($request->user['id']);
if ($user->commission_rate) {
$commission_rate = $user->commission_rate;
}
$uncheck_commission_balance = (int)Order::where('status', 3)
->where('commission_status', 0)
->where('invite_user_id', $request->user['id'])
->sum('commission_balance');
if (admin_setting('commission_distribution_enable', 0)) {
$uncheck_commission_balance = $uncheck_commission_balance * (admin_setting('commission_distribution_l1') / 100);
}
$stat = [
//已注册用户数
(int)User::where('invite_user_id', $request->user['id'])->count(),
//有效的佣金
(int)CommissionLog::where('invite_user_id', $request->user['id'])
->sum('get_amount'),
//确认中的佣金
$uncheck_commission_balance,
//佣金比例
(int)$commission_rate,
//可用佣金
(int)$user->commission_balance
];
return response([
'data' => [
'codes' => $codes,
'stat' => $stat
]
]);
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Models\Knowledge;
use App\Models\User;
use App\Services\UserService;
use App\Utils\Helper;
use Illuminate\Http\Request;
class KnowledgeController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$knowledge = Knowledge::where('id', $request->input('id'))
->where('show', 1)
->first()
->toArray();
if (!$knowledge) abort(500, __('Article does not exist'));
$user = User::find($request->user['id']);
$userService = new UserService();
if (!$userService->isAvailable($user)) {
$this->formatAccessData($knowledge['body']);
}
$subscribeUrl = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}");
$knowledge['body'] = str_replace('{{siteName}}', admin_setting('app_name', 'XBoard'), $knowledge['body']);
$knowledge['body'] = str_replace('{{subscribeUrl}}', $subscribeUrl, $knowledge['body']);
$knowledge['body'] = str_replace('{{urlEncodeSubscribeUrl}}', urlencode($subscribeUrl), $knowledge['body']);
$knowledge['body'] = str_replace(
'{{safeBase64SubscribeUrl}}',
str_replace(
array('+', '/', '='),
array('-', '_', ''),
base64_encode($subscribeUrl)
),
$knowledge['body']
);
return response([
'data' => $knowledge
]);
}
$builder = Knowledge::select(['id', 'category', 'title', 'updated_at'])
->where('language', $request->input('language'))
->where('show', 1)
->orderBy('sort', 'ASC');
$keyword = $request->input('keyword');
if ($keyword) {
$builder = $builder->where(function ($query) use ($keyword) {
$query->where('title', 'LIKE', "%{$keyword}%")
->orWhere('body', 'LIKE', "%{$keyword}%");
});
}
$knowledges = $builder->get()
->groupBy('category');
return response([
'data' => $knowledges
]);
}
private function formatAccessData(&$body)
{
function getBetween($input, $start, $end){$substr = substr($input, strlen($start)+strpos($input, $start),(strlen($input) - strpos($input, $end))*(-1));return $start . $substr . $end;}
while (strpos($body, '<!--access start-->') !== false) {
$accessData = getBetween($body, '<!--access start-->', '<!--access end-->');
if ($accessData) {
$body = str_replace($accessData, '<div class="v2board-no-access">'. __('You must have a valid subscription to view content in this area') .'</div>', $body);
}
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Models\Notice;
use Illuminate\Http\Request;
class NoticeController extends Controller
{
public function fetch(Request $request)
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = 5;
$model = Notice::orderBy('created_at', 'DESC')
->where('show', 1);
$total = $model->count();
$res = $model->forPage($current, $pageSize)
->get();
return response([
'data' => $res,
'total' => $total
]);
}
}

View File

@@ -0,0 +1,266 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\OrderSave;
use App\Models\Order;
use App\Models\Payment;
use App\Models\Plan;
use App\Models\User;
use App\Services\CouponService;
use App\Services\OrderService;
use App\Services\PaymentService;
use App\Services\PlanService;
use App\Services\UserService;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Library\BitpayX;
use Library\Epay;
use Library\MGate;
use Omnipay\Omnipay;
use Stripe\Source;
use Stripe\Stripe;
class OrderController extends Controller
{
public function fetch(Request $request)
{
$model = Order::where('user_id', $request->user['id'])
->orderBy('created_at', 'DESC');
if ($request->input('status') !== null) {
$model->where('status', $request->input('status'));
}
$order = $model->get();
$plan = Plan::get();
for ($i = 0; $i < count($order); $i++) {
for ($x = 0; $x < count($plan); $x++) {
if ($order[$i]['plan_id'] === $plan[$x]['id']) {
$order[$i]['plan'] = $plan[$x];
}
}
}
return response([
'data' => $order->makeHidden(['id', 'user_id'])
]);
}
public function detail(Request $request)
{
$order = Order::where('user_id', $request->user['id'])
->where('trade_no', $request->input('trade_no'))
->first();
if (!$order) {
abort(500, __('Order does not exist or has been paid'));
}
$order['plan'] = Plan::find($order->plan_id);
$order['try_out_plan_id'] = (int)admin_setting('try_out_plan_id');
if (!$order['plan']) {
abort(500, __('Subscription plan does not exist'));
}
if ($order->surplus_order_ids) {
$order['surplus_orders'] = Order::whereIn('id', $order->surplus_order_ids)->get();
}
return response([
'data' => $order
]);
}
public function save(OrderSave $request)
{
$userService = new UserService();
if ($userService->isNotCompleteOrderByUserId($request->user['id'])) {
abort(500, __('You have an unpaid or pending order, please try again later or cancel it'));
}
$planService = new PlanService($request->input('plan_id'));
$plan = $planService->plan;
$user = User::find($request->user['id']);
if (!$plan) {
abort(500, __('Subscription plan does not exist'));
}
if ($user->plan_id !== $plan->id && !$planService->haveCapacity() && $request->input('period') !== 'reset_price') {
abort(500, __('Current product is sold out'));
}
if ($plan[$request->input('period')] === NULL) {
abort(500, __('This payment period cannot be purchased, please choose another period'));
}
if ($request->input('period') === 'reset_price') {
if (!$userService->isAvailable($user) || $plan->id !== $user->plan_id) {
abort(500, __('Subscription has expired or no active subscription, unable to purchase Data Reset Package'));
}
}
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
if ($request->input('period') !== 'reset_price') {
abort(500, __('This subscription has been sold out, please choose another subscription'));
}
}
if (!$plan->renew && $user->plan_id == $plan->id && $request->input('period') !== 'reset_price') {
abort(500, __('This subscription cannot be renewed, please change to another subscription'));
}
if (!$plan->show && $plan->renew && !$userService->isAvailable($user)) {
abort(500, __('This subscription has expired, please change to another subscription'));
}
DB::beginTransaction();
$order = new Order();
$orderService = new OrderService($order);
$order->user_id = $request->user['id'];
$order->plan_id = $plan->id;
$order->period = $request->input('period');
$order->trade_no = Helper::generateOrderNo();
$order->total_amount = $plan[$request->input('period')];
if ($request->input('coupon_code')) {
$couponService = new CouponService($request->input('coupon_code'));
if (!$couponService->use($order)) {
DB::rollBack();
abort(500, __('Coupon failed'));
}
$order->coupon_id = $couponService->getId();
}
$orderService->setVipDiscount($user);
$orderService->setOrderType($user);
$orderService->setInvite($user);
if ($user->balance && $order->total_amount > 0) {
$remainingBalance = $user->balance - $order->total_amount;
$userService = new UserService();
if ($remainingBalance > 0) {
if (!$userService->addBalance($order->user_id, - $order->total_amount)) {
DB::rollBack();
abort(500, __('Insufficient balance'));
}
$order->balance_amount = $order->total_amount;
$order->total_amount = 0;
} else {
if (!$userService->addBalance($order->user_id, - $user->balance)) {
DB::rollBack();
abort(500, __('Insufficient balance'));
}
$order->balance_amount = $user->balance;
$order->total_amount = $order->total_amount - $user->balance;
}
}
if (!$order->save()) {
DB::rollback();
abort(500, __('Failed to create order'));
}
DB::commit();
return response([
'data' => $order->trade_no
]);
}
public function checkout(Request $request)
{
$tradeNo = $request->input('trade_no');
$method = $request->input('method');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->user['id'])
->where('status', 0)
->first();
if (!$order) {
abort(500, __('Order does not exist or has been paid'));
}
// free process
if ($order->total_amount <= 0) {
$orderService = new OrderService($order);
if (!$orderService->paid($order->trade_no)) abort(500, '');
return response([
'type' => -1,
'data' => true
]);
}
$payment = Payment::find($method);
if (!$payment || $payment->enable !== 1) abort(500, __('Payment method is not available'));
$paymentService = new PaymentService($payment->payment, $payment->id);
$order->handling_amount = NULL;
if ($payment->handling_fee_fixed || $payment->handling_fee_percent) {
$order->handling_amount = round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed);
}
$order->payment_id = $method;
if (!$order->save()) abort(500, __('Request failed, please try again later'));
$result = $paymentService->pay([
'trade_no' => $tradeNo,
'total_amount' => isset($order->handling_amount) ? ($order->total_amount + $order->handling_amount) : $order->total_amount,
'user_id' => $order->user_id,
'stripe_token' => $request->input('token')
]);
return response([
'type' => $result['type'],
'data' => $result['data']
]);
}
public function check(Request $request)
{
$tradeNo = $request->input('trade_no');
$order = Order::where('trade_no', $tradeNo)
->where('user_id', $request->user['id'])
->first();
if (!$order) {
abort(500, __('Order does not exist'));
}
return response([
'data' => $order->status
]);
}
public function getPaymentMethod()
{
$methods = Payment::select([
'id',
'name',
'payment',
'icon',
'handling_fee_fixed',
'handling_fee_percent'
])
->where('enable', 1)
->orderBy('sort', 'ASC')
->get();
return response([
'data' => $methods
]);
}
public function cancel(Request $request)
{
if (empty($request->input('trade_no'))) {
abort(500, __('Invalid parameter'));
}
$order = Order::where('trade_no', $request->input('trade_no'))
->where('user_id', $request->user['id'])
->first();
if (!$order) {
abort(500, __('Order does not exist'));
}
if ($order->status !== 0) {
abort(500, __('You can only cancel pending orders'));
}
$orderService = new OrderService($order);
if (!$orderService->cancel()) {
abort(500, __('Cancel failed'));
}
return response([
'data' => true
]);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\User;
use App\Services\PlanService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class PlanController extends Controller
{
public function fetch(Request $request)
{
$user = User::find($request->user['id']);
if ($request->input('id')) {
$plan = Plan::where('id', $request->input('id'))->first();
if (!$plan) {
abort(500, __('Subscription plan does not exist'));
}
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
abort(500, __('Subscription plan does not exist'));
}
return response([
'data' => $plan
]);
}
$counts = PlanService::countActiveUsers();
$plans = Plan::where('show', 1)
->orderBy('sort', 'ASC')
->get();
foreach ($plans as $k => $v) {
if ($plans[$k]->capacity_limit === NULL) continue;
if (!isset($counts[$plans[$k]->id])) continue;
$plans[$k]->capacity_limit = $plans[$k]->capacity_limit - $counts[$plans[$k]->id]->count;
}
return response([
'data' => $plans
]);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\ServerService;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class ServerController extends Controller
{
public function fetch(Request $request)
{
$user = User::find($request->user['id']);
$servers = [];
$userService = new UserService();
if ($userService->isAvailable($user)) {
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
}
$eTag = sha1(json_encode(array_column($servers, 'cache_key')));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
return response(null,304);
}
return response([
'data' => $servers
])->header('ETag', "\"{$eTag}\"");
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Models\StatUser;
use App\Services\StatisticalService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class StatController extends Controller
{
public function getTrafficLog(Request $request)
{
$records = StatUser::select([
'u',
'd',
'record_at',
'user_id',
'server_rate'
])
->where('user_id', $request->user['id'])
->where('record_at', '>=', strtotime(date('Y-m-1')))
->orderBy('record_at', 'DESC')
->get();
// 追加当天流量
$recordAt = strtotime(date('Y-m-d'));
$statService = new StatisticalService();
$statService->setStartAt($recordAt);
$statService->setUserStats();
$todayTraffics = $statService->getStatUserByUserID($request->user['id']);
if (count($todayTraffics) > 0) {
foreach ($todayTraffics as $todayTraffic){
$todayTraffic['server_rate'] = number_format($todayTraffic['server_rate'], 2);
$records->prepend($todayTraffic);
}
};
return response([
'data' => $records
]);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\TelegramService;
use Illuminate\Http\Request;
class TelegramController extends Controller
{
public function getBotInfo()
{
$telegramService = new TelegramService();
$response = $telegramService->getMe();
return response([
'data' => [
'username' => $response->result->username
]
]);
}
public function unbind(Request $request)
{
$user = User::where('user_id', $request->user['id'])->first();
}
}

View File

@@ -0,0 +1,194 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\TicketSave;
use App\Http\Requests\User\TicketWithdraw;
use App\Models\Ticket;
use App\Models\TicketMessage;
use App\Models\User;
use App\Services\TelegramService;
use App\Services\TicketService;
use App\Utils\Dict;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class TicketController extends Controller
{
public function fetch(Request $request)
{
if ($request->input('id')) {
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->user['id'])
->first();
if (!$ticket) {
abort(500, __('Ticket does not exist'));
}
$ticket['message'] = TicketMessage::where('ticket_id', $ticket->id)->get();
for ($i = 0; $i < count($ticket['message']); $i++) {
if ($ticket['message'][$i]['user_id'] == $ticket->user_id) {
$ticket['message'][$i]['is_me'] = true;
} else {
$ticket['message'][$i]['is_me'] = false;
}
}
return response([
'data' => $ticket
]);
}
$ticket = Ticket::where('user_id', $request->user['id'])
->orderBy('created_at', 'DESC')
->get();
return response([
'data' => $ticket
]);
}
public function save(TicketSave $request)
{
DB::beginTransaction();
if ((int)Ticket::where('status', 0)->where('user_id', $request->user['id'])->lockForUpdate()->count()) {
abort(500, __('There are other unresolved tickets'));
}
$ticket = Ticket::create(array_merge($request->only([
'subject',
'level'
]), [
'user_id' => $request->user['id']
]));
if (!$ticket) {
DB::rollback();
abort(500, __('Failed to open ticket'));
}
$ticketMessage = TicketMessage::create([
'user_id' => $request->user['id'],
'ticket_id' => $ticket->id,
'message' => $request->input('message')
]);
if (!$ticketMessage) {
DB::rollback();
abort(500, __('Failed to open ticket'));
}
DB::commit();
$this->sendNotify($ticket, $request->input('message'));
return response([
'data' => true
]);
}
public function reply(Request $request)
{
if (empty($request->input('id'))) {
abort(500, __('Invalid parameter'));
}
if (empty($request->input('message'))) {
abort(500, __('Message cannot be empty'));
}
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->user['id'])
->first();
if (!$ticket) {
abort(500, __('Ticket does not exist'));
}
if ($ticket->status) {
abort(500, __('The ticket is closed and cannot be replied'));
}
if ($request->user['id'] == $this->getLastMessage($ticket->id)->user_id) {
abort(500, __('Please wait for the technical enginneer to reply'));
}
$ticketService = new TicketService();
if (!$ticketService->reply(
$ticket,
$request->input('message'),
$request->user['id']
)) {
abort(500, __('Ticket reply failed'));
}
$this->sendNotify($ticket, $request->input('message'));
return response([
'data' => true
]);
}
public function close(Request $request)
{
if (empty($request->input('id'))) {
abort(500, __('Invalid parameter'));
}
$ticket = Ticket::where('id', $request->input('id'))
->where('user_id', $request->user['id'])
->first();
if (!$ticket) {
abort(500, __('Ticket does not exist'));
}
$ticket->status = 1;
if (!$ticket->save()) {
abort(500, __('Close failed'));
}
return response([
'data' => true
]);
}
private function getLastMessage($ticketId)
{
return TicketMessage::where('ticket_id', $ticketId)
->orderBy('id', 'DESC')
->first();
}
public function withdraw(TicketWithdraw $request)
{
if ((int)admin_setting('withdraw_close_enable', 0)) {
abort(500, 'user.ticket.withdraw.not_support_withdraw');
}
if (!in_array(
$request->input('withdraw_method'),
admin_setting('commission_withdraw_method',Dict::WITHDRAW_METHOD_WHITELIST_DEFAULT)
)) {
abort(500, __('Unsupported withdrawal method'));
}
$user = User::find($request->user['id']);
$limit = admin_setting('commission_withdraw_limit', 100);
if ($limit > ($user->commission_balance / 100)) {
abort(500, __('The current required minimum withdrawal commission is :limit', ['limit' => $limit]));
}
DB::beginTransaction();
$subject = __('[Commission Withdrawal Request] This ticket is opened by the system');
$ticket = Ticket::create([
'subject' => $subject,
'level' => 2,
'user_id' => $request->user['id']
]);
if (!$ticket) {
DB::rollback();
abort(500, __('Failed to open ticket'));
}
$message = sprintf("%s\r\n%s",
__('Withdrawal method') . "" . $request->input('withdraw_method'),
__('Withdrawal account') . "" . $request->input('withdraw_account')
);
$ticketMessage = TicketMessage::create([
'user_id' => $request->user['id'],
'ticket_id' => $ticket->id,
'message' => $message
]);
if (!$ticketMessage) {
DB::rollback();
abort(500, __('Failed to open ticket'));
}
DB::commit();
$this->sendNotify($ticket, $message);
return response([
'data' => true
]);
}
private function sendNotify(Ticket $ticket, string $message)
{
$telegramService = new TelegramService();
$telegramService->sendMessageWithAdmin("📮工单提醒 #{$ticket->id}\n———————————————\n主题:\n`{$ticket->subject}`\n内容:\n`{$message}`", true);
}
}

View File

@@ -0,0 +1,239 @@
<?php
namespace App\Http\Controllers\V1\User;
use App\Http\Controllers\Controller;
use App\Http\Requests\User\UserChangePassword;
use App\Http\Requests\User\UserTransfer;
use App\Http\Requests\User\UserUpdate;
use App\Models\Order;
use App\Models\Plan;
use App\Models\Ticket;
use App\Models\User;
use App\Services\AuthService;
use App\Services\UserService;
use App\Utils\CacheKey;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
public function getActiveSession(Request $request)
{
$user = User::find($request->user['id']);
if (!$user) {
abort(500, __('The user does not exist'));
}
$authService = new AuthService($user);
return response([
'data' => $authService->getSessions()
]);
}
public function removeActiveSession(Request $request)
{
$user = User::find($request->user['id']);
if (!$user) {
abort(500, __('The user does not exist'));
}
$authService = new AuthService($user);
return response([
'data' => $authService->removeSession($request->input('session_id'))
]);
}
public function checkLogin(Request $request)
{
$data = [
'is_login' => $request->user['id'] ? true : false
];
if ($request->user['is_admin']) {
$data['is_admin'] = true;
}
return response([
'data' => $data
]);
}
public function changePassword(UserChangePassword $request)
{
$user = User::find($request->user['id']);
if (!$user) {
abort(500, __('The user does not exist'));
}
if (!Helper::multiPasswordVerify(
$user->password_algo,
$user->password_salt,
$request->input('old_password'),
$user->password)
) {
abort(500, __('The old password is wrong'));
}
$user->password = password_hash($request->input('new_password'), PASSWORD_DEFAULT);
$user->password_algo = NULL;
$user->password_salt = NULL;
if (!$user->save()) {
abort(500, __('Save failed'));
}
return response([
'data' => true
]);
}
public function info(Request $request)
{
$user = User::where('id', $request->user['id'])
->select([
'email',
'transfer_enable',
'last_login_at',
'created_at',
'banned',
'remind_expire',
'remind_traffic',
'expired_at',
'balance',
'commission_balance',
'plan_id',
'discount',
'commission_rate',
'telegram_id',
'uuid'
])
->first();
if (!$user) {
abort(500, __('The user does not exist'));
}
$user['avatar_url'] = 'https://cdn.v2ex.com/gravatar/' . md5($user->email) . '?s=64&d=identicon';
return response([
'data' => $user
]);
}
public function getStat(Request $request)
{
$stat = [
Order::where('status', 0)
->where('user_id', $request->user['id'])
->count(),
Ticket::where('status', 0)
->where('user_id', $request->user['id'])
->count(),
User::where('invite_user_id', $request->user['id'])
->count()
];
return response([
'data' => $stat
]);
}
public function getSubscribe(Request $request)
{
$user = User::where('id', $request->user['id'])
->select([
'plan_id',
'token',
'expired_at',
'u',
'd',
'transfer_enable',
'email',
'uuid'
])
->first();
if (!$user) {
abort(500, __('The user does not exist'));
}
if ($user->plan_id) {
$user['plan'] = Plan::find($user->plan_id);
if (!$user['plan']) {
abort(500, __('Subscription plan does not exist'));
}
}
$user['subscribe_url'] = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}");
$userService = new UserService();
$user['reset_day'] = $userService->getResetDay($user);
return response([
'data' => $user
]);
}
public function resetSecurity(Request $request)
{
$user = User::find($request->user['id']);
if (!$user) {
abort(500, __('The user does not exist'));
}
$user->uuid = Helper::guid(true);
$user->token = Helper::guid();
if (!$user->save()) {
abort(500, __('Reset failed'));
}
return response([
'data' => Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user->token)
]);
}
public function update(UserUpdate $request)
{
$updateData = $request->only([
'remind_expire',
'remind_traffic'
]);
$user = User::find($request->user['id']);
if (!$user) {
abort(500, __('The user does not exist'));
}
try {
$user->update($updateData);
} catch (\Exception $e) {
abort(500, __('Save failed'));
}
return response([
'data' => true
]);
}
public function transfer(UserTransfer $request)
{
$user = User::find($request->user['id']);
if (!$user) {
abort(500, __('The user does not exist'));
}
if ($request->input('transfer_amount') > $user->commission_balance) {
abort(500, __('Insufficient commission balance'));
}
$user->commission_balance = $user->commission_balance - $request->input('transfer_amount');
$user->balance = $user->balance + $request->input('transfer_amount');
if (!$user->save()) {
abort(500, __('Transfer failed'));
}
return response([
'data' => true
]);
}
public function getQuickLoginUrl(Request $request)
{
$user = User::find($request->user['id']);
if (!$user) {
abort(500, __('The user does not exist'));
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 60);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (admin_setting('app_url')) {
$url = admin_setting('app_url') . $redirect;
} else {
$url = url($redirect);
}
return response([
'data' => $url
]);
}
}