Merge pull request #327 from v2board/dev

1.4
This commit is contained in:
tokumeikoi 2020-11-01 16:50:50 +08:00 committed by GitHub
commit c6be6b2fbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 2171 additions and 601 deletions

View File

@ -7,8 +7,6 @@ use Illuminate\Console\Command;
use App\Models\Order; use App\Models\Order;
use App\Models\User; use App\Models\User;
use App\Models\Plan; use App\Models\Plan;
use App\Utils\Helper;
use App\Models\Coupon;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class CheckOrder extends Command class CheckOrder extends Command
@ -46,113 +44,19 @@ class CheckOrder extends Command
{ {
$orders = Order::get(); $orders = Order::get();
foreach ($orders as $item) { foreach ($orders as $item) {
$orderService = new OrderService($item);
switch ($item->status) { switch ($item->status) {
// cancel // cancel
case 0: case 0:
if (strtotime($item->created_at) <= (time() - 1800)) { if (strtotime($item->created_at) <= (time() - 1800)) {
$orderService = new OrderService($item);
$orderService->cancel(); $orderService->cancel();
} }
break; break;
case 1: case 1:
$this->orderHandle($item); $orderService->open();
break; break;
} }
} }
} }
private function orderHandle(Order $order)
{
$user = User::find($order->user_id);
$plan = Plan::find($order->plan_id);
if ($order->refund_amount) {
$user->balance = $user->balance + $order->refund_amount;
}
DB::beginTransaction();
if ($order->surplus_order_ids) {
try {
Order::whereIn('id', json_decode($order->surplus_order_ids))->update([
'status' => 4
]);
} catch (\Exception $e) {
DB::rollback();
abort(500, '开通失败');
}
}
switch ((string)$order->cycle) {
case 'onetime_price':
$this->buyByOneTime($order, $user, $plan);
break;
case 'reset_price':
$this->buyReset($user);
break;
default:
$this->buyByCycle($order, $user, $plan);
}
if (!$user->save()) {
DB::rollBack();
abort(500, '开通失败');
}
$order->status = 3;
if (!$order->save()) {
DB::rollBack();
abort(500, '开通失败');
}
DB::commit();
}
private function buyReset(User $user)
{
$user->u = 0;
$user->d = 0;
}
private function buyByCycle(Order $order, User $user, Plan $plan)
{
// change plan process
if ((int)$order->type === 3) {
$user->expired_at = time();
}
$user->transfer_enable = $plan->transfer_enable * 1073741824;
// 续费重置&类型=续费
if ((int)config('v2board.renew_reset_traffic_enable', 1) && $order->type === 2) $this->buyReset($user);
// 购买前用户过期为NULL一次性
if ($user->expired_at === NULL) $this->buyReset($user);
// 新购
if ($order->type === 1) $this->buyReset($user);
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = $this->getTime($order->cycle, $user->expired_at);
}
private function buyByOneTime(Order $order, User $user, Plan $plan)
{
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->u = 0;
$user->d = 0;
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = NULL;
}
private function getTime($str, $timestamp)
{
if ($timestamp < time()) {
$timestamp = time();
}
switch ($str) {
case 'month_price':
return strtotime('+1 month', $timestamp);
case 'quarter_price':
return strtotime('+3 month', $timestamp);
case 'half_year_price':
return strtotime('+6 month', $timestamp);
case 'year_price':
return strtotime('+12 month', $timestamp);
}
}
} }

View File

@ -7,7 +7,7 @@ use App\Models\User;
class ResetTraffic extends Command class ResetTraffic extends Command
{ {
protected $user; protected $builder;
/** /**
* The name and signature of the console command. * The name and signature of the console command.
* *
@ -30,7 +30,7 @@ class ResetTraffic extends Command
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
$this->user = User::where('expired_at', '!=', NULL) $this->builder = User::where('expired_at', '!=', NULL)
->where('expired_at', '>', time()); ->where('expired_at', '>', time());
} }
@ -54,11 +54,11 @@ class ResetTraffic extends Command
} }
} }
private function resetByMonthFirstDay($user):void private function resetByMonthFirstDay():void
{ {
$user = $this->user; $builder = $this->builder;
if ((string)date('d') === '01') { if ((string)date('d') === '01') {
$user->update([ $builder->update([
'u' => 0, 'u' => 0,
'd' => 0 'd' => 0
]); ]);
@ -67,10 +67,10 @@ class ResetTraffic extends Command
private function resetByExpireDay():void private function resetByExpireDay():void
{ {
$user = $this->user; $builder = $this->builder;
$lastDay = date('d', strtotime('last day of +0 months')); $lastDay = date('d', strtotime('last day of +0 months'));
$users = []; $users = [];
foreach ($user->get() as $item) { foreach ($builder->get() as $item) {
$expireDay = date('d', $item->expired_at); $expireDay = date('d', $item->expired_at);
$today = date('d'); $today = date('d');
if ($expireDay === $today) { if ($expireDay === $today) {

View File

@ -43,7 +43,6 @@ class SendRemindMail extends Command
$users = User::all(); $users = User::all();
foreach ($users as $user) { foreach ($users as $user) {
if ($user->remind_expire) $this->remindExpire($user); if ($user->remind_expire) $this->remindExpire($user);
if ($user->remind_traffic) $this->remindTraffic($user);
} }
} }
@ -61,31 +60,4 @@ class SendRemindMail extends Command
]); ]);
} }
} }
private function remindTraffic($user)
{
if ($this->remindTrafficIsWarnValue(($user->u + $user->d), $user->transfer_enable)) {
$sendCount = MailLog::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('template_name', 'like', '%remindTraffic%')
->count();
if ($sendCount > 0) return;
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的流量使用已达到80%',
'template_name' => 'remindTraffic',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url')
]
]);
}
}
private function remindTrafficIsWarnValue($ud, $transfer_enable)
{
if ($ud <= 0) return false;
if (($ud / $transfer_enable * 100) < 80) return false;
return true;
}
} }

View File

@ -46,7 +46,8 @@ class ConfigController extends Controller
'invite_gen_limit' => config('v2board.invite_gen_limit', 5), 'invite_gen_limit' => config('v2board.invite_gen_limit', 5),
'invite_never_expire' => config('v2board.invite_never_expire', 0), 'invite_never_expire' => config('v2board.invite_never_expire', 0),
'commission_first_time_enable' => config('v2board.commission_first_time_enable', 1), 'commission_first_time_enable' => config('v2board.commission_first_time_enable', 1),
'commission_auto_check_enable' => config('v2board.commission_auto_check_enable', 1) 'commission_auto_check_enable' => config('v2board.commission_auto_check_enable', 1),
'commission_withdraw_limit' => config('v2board.commission_withdraw_limit', 100)
], ],
'site' => [ 'site' => [
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0), 'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
@ -60,12 +61,16 @@ class ConfigController extends Controller
'try_out_hour' => (int)config('v2board.try_out_hour', 1), 'try_out_hour' => (int)config('v2board.try_out_hour', 1),
'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0), 'email_whitelist_enable' => (int)config('v2board.email_whitelist_enable', 0),
'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT), 'email_whitelist_suffix' => config('v2board.email_whitelist_suffix', Dict::EMAIL_WHITELIST_SUFFIX_DEFAULT),
'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0) 'email_gmail_limit_enable' => config('v2board.email_gmail_limit_enable', 0),
'recaptcha_enable' => (int)config('v2board.recaptcha_enable', 0),
'recaptcha_key' => config('v2board.recaptcha_key'),
'recaptcha_site_key' => config('v2board.recaptcha_site_key')
], ],
'subscribe' => [ 'subscribe' => [
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1), 'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0), 'reset_traffic_method' => (int)config('v2board.reset_traffic_method', 0),
'renew_reset_traffic_enable' => (int)config('v2board.renew_reset_traffic_enable', 1) 'renew_reset_traffic_enable' => (int)config('v2board.renew_reset_traffic_enable', 0),
'surplus_enable' => (int)config('v2board.surplus_enable', 1)
], ],
'pay' => [ 'pay' => [
// alipay // alipay
@ -147,7 +152,9 @@ class ConfigController extends Controller
abort(500, '修改失败'); abort(500, '修改失败');
} }
if (function_exists('opcache_reset')) { if (function_exists('opcache_reset')) {
opcache_reset(); if (!opcache_reset()) {
abort(500, '缓存清除失败请卸载或检查opcache配置状态');
}
} }
\Artisan::call('config:cache'); \Artisan::call('config:cache');
return response([ return response([

View File

@ -3,21 +3,34 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\CouponSave; use App\Http\Requests\Admin\CouponSave;
use App\Http\Requests\Admin\CouponGenerate;
use App\Models\Plan;
use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Coupon; use App\Models\Coupon;
use App\Utils\Helper; use App\Utils\Helper;
use Illuminate\Support\Facades\DB;
class CouponController extends Controller class CouponController extends Controller
{ {
public function fetch(Request $request) public function fetch(Request $request)
{ {
$coupons = Coupon::all(); $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';
$builder = Coupon::orderBy($sort, $sortType);
$total = $builder->count();
$coupons = $builder->forPage($current, $pageSize)
->get();
foreach ($coupons as $k => $v) { foreach ($coupons as $k => $v) {
if ($coupons[$k]['limit_plan_ids']) $coupons[$k]['limit_plan_ids'] = json_decode($coupons[$k]['limit_plan_ids']); if ($coupons[$k]['limit_plan_ids']) $coupons[$k]['limit_plan_ids'] = json_decode($coupons[$k]['limit_plan_ids']);
} }
return response([ return response([
'data' => $coupons 'data' => $coupons,
'total' => $total
]); ]);
} }
@ -47,6 +60,67 @@ class CouponController extends Controller
]); ]);
} }
public function generate(CouponGenerate $request)
{
if ($request->input('generate_count')) {
$this->multiGenerate($request);
return;
}
$params = $request->validated();
if (isset($params['limit_plan_ids'])) {
$params['limit_plan_ids'] = json_encode($params['limit_plan_ids']);
}
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 = [];
for ($i = 0;$i < $request->input('generate_count');$i++) {
$coupon = $request->validated();
$coupon['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
$coupon['code'] = Helper::randomChar(8);
$coupon['created_at'] = $coupon['updated_at'] = time();
unset($coupon['generate_count']);
array_push($coupons, $coupon);
}
DB::beginTransaction();
if (!Coupon::insert($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']);
$data .= "{$coupon['name']},{$type},{$value},{$startTime},{$endTime},{$limitUse},{$coupon['limit_plan_ids']},{$coupon['code']},{$createTime}\r\n";
}
echo $data;
}
public function drop(Request $request) public function drop(Request $request)
{ {
if (empty($request->input('id'))) { if (empty($request->input('id'))) {

View File

@ -0,0 +1,109 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\KnowledgeSave;
use App\Http\Requests\Admin\KnowledgeSort;
use App\Models\Knowledge;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
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();
foreach ($request->input('knowledge_ids') as $k => $v) {
if (!Knowledge::find($v)->update(['sort' => $k + 1])) {
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

@ -1,47 +0,0 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\MailSend;
use App\Services\UserService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Jobs\SendEmailJob;
class MailController extends Controller
{
public function send(MailSend $request)
{
$userService = new UserService();
$users = [];
switch ($request->input('type')) {
case 1: $users = $userService->getAllUsers();
break;
case 2: $users = $userService->getUsersByIds($request->input('receiver'));
break;
// available users
case 3: $users = $userService->getAvailableUsers();
break;
// un available users
case 4: $users = $userService->getUnAvailbaleUsers();
break;
}
foreach ($users as $user) {
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => $request->input('subject'),
'template_name' => 'notify',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url'),
'content' => $request->input('content')
]
]);
}
return response([
'data' => true
]);
}
}

View File

@ -0,0 +1,135 @@
<?php
namespace App\Http\Controllers\Admin\Server;
use App\Http\Requests\Admin\ServerShadowsocksSave;
use App\Http\Requests\Admin\ServerShadowsocksSort;
use App\Http\Requests\Admin\ServerShadowsocksUpdate;
use App\Models\ServerShadowsocks;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Server;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class ShadowsocksController extends Controller
{
public function fetch(Request $request)
{
$server = ServerShadowsocks::orderBy('sort', 'ASC')->get();
for ($i = 0; $i < count($server); $i++) {
if (!empty($server[$i]['tags'])) {
$server[$i]['tags'] = json_decode($server[$i]['tags']);
}
$server[$i]['group_id'] = json_decode($server[$i]['group_id']);
$server[$i]['online'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server[$i]['parent_id'] ? $server[$i]['parent_id'] : $server[$i]['id']));
if ($server[$i]['parent_id']) {
$server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server[$i]['parent_id']));
} else {
$server[$i]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server[$i]['id']));
}
}
return response([
'data' => $server
]);
}
public function save(ServerShadowsocksSave $request)
{
$params = $request->validated();
$params['group_id'] = json_encode($params['group_id']);
if (isset($params['tags'])) {
$params['tags'] = json_encode($params['tags']);
}
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
]);
}
public function sort(ServerShadowsocksSort $request)
{
DB::beginTransaction();
foreach ($request->input('server_ids') as $k => $v) {
if (!ServerShadowsocks::find($v)->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
}
DB::commit();
return response([
'data' => true
]);
}
}

View File

@ -1,93 +0,0 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Requests\Admin\TutorialSave;
use App\Http\Requests\Admin\TutorialSort;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Tutorial;
use Illuminate\Support\Facades\DB;
class TutorialController extends Controller
{
public function fetch(Request $request)
{
return response([
'data' => Tutorial::orderBy('sort', 'ASC')->get()
]);
}
public function save(TutorialSave $request)
{
$params = $request->validated();
if (!$request->input('id')) {
if (!Tutorial::create($params)) {
abort(500, '创建失败');
}
} else {
try {
Tutorial::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, '参数有误');
}
$tutorial = Tutorial::find($request->input('id'));
if (!$tutorial) {
abort(500, '教程不存在');
}
$tutorial->show = $tutorial->show ? 0 : 1;
if (!$tutorial->save()) {
abort(500, '保存失败');
}
return response([
'data' => true
]);
}
public function sort(TutorialSort $request)
{
DB::beginTransaction();
foreach ($request->input('tutorial_ids') as $k => $v) {
if (!Tutorial::find($v)->update(['sort' => $k + 1])) {
DB::rollBack();
abort(500, '保存失败');
}
}
DB::commit();
return response([
'data' => true
]);
}
public function drop(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数有误');
}
$tutorial = Tutorial::find($request->input('id'));
if (!$tutorial) {
abort(500, '教程不存在');
}
if (!$tutorial->delete()) {
abort(500, '删除失败');
}
return response([
'data' => true
]);
}
}

View File

@ -2,28 +2,52 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
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\Http\Requests\Admin\UserUpdate;
use App\Jobs\SendEmailJob;
use App\Utils\Helper;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Order; use App\Models\Order;
use App\Models\User; use App\Models\User;
use App\Models\Plan; use App\Models\Plan;
use Illuminate\Support\Facades\DB;
class UserController extends Controller class UserController extends Controller
{ {
public function fetch(Request $request)
private function filter(Request $request, $builder)
{
if ($request->input('filter')) {
foreach ($request->input('filter') as $filter) {
if ($filter['key'] === 'invite_by_email') {
$user = User::where('email', $filter['value'])->first();
if (!$user) continue;
$builder->where('invite_user_id', $user->id);
continue;
}
if ($filter['key'] === 'd' || $filter['key'] === 'transfer_enable') {
$filter['value'] = $filter['value'] * 1073741824;
}
if ($filter['condition'] === '模糊') {
$filter['condition'] = 'like';
$filter['value'] = "%{$filter['value']}%";
}
$builder->where($filter['key'], $filter['condition'], $filter['value']);
}
}
}
public function fetch(UserFetch $request)
{ {
$current = $request->input('current') ? $request->input('current') : 1; $current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10; $pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
$sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC'; $sortType = in_array($request->input('sort_type'), ['ASC', 'DESC']) ? $request->input('sort_type') : 'DESC';
$sort = $request->input('sort') ? $request->input('sort') : 'created_at'; $sort = $request->input('sort') ? $request->input('sort') : 'created_at';
$userModel = User::orderBy($sort, $sortType); $userModel = User::orderBy($sort, $sortType);
if ($request->input('email')) { $this->filter($request, $userModel);
$userModel->where('email', $request->input('email'));
}
if ($request->input('invite_user_id')) {
$userModel->where('invite_user_id', $request->input('invite_user_id'));
}
$total = $userModel->count(); $total = $userModel->count();
$res = $userModel->forPage($current, $pageSize) $res = $userModel->forPage($current, $pageSize)
->get(); ->get();
@ -84,4 +108,150 @@ class UserController extends Controller
'data' => true '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";
$baseUrl = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL')));
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 = $baseUrl . '/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()
];
$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";
$baseUrl = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL')));
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 = $baseUrl . '/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' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.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

@ -18,7 +18,7 @@ class AppController extends Controller
public function getConfig(Request $request) public function getConfig(Request $request)
{ {
$server = []; $servers = [];
$user = $request->user; $user = $request->user;
$userService = new UserService(); $userService = new UserService();
if ($userService->isAvailable($user)) { if ($userService->isAvailable($user)) {
@ -29,6 +29,11 @@ class AppController extends Controller
$proxy = []; $proxy = [];
$proxies = []; $proxies = [];
foreach ($servers['shadowsocks'] as $item) {
array_push($proxy, Clash::buildShadowsocks($user->uuid, $item));
array_push($proxies, $item->name);
}
foreach ($servers['vmess'] as $item) { foreach ($servers['vmess'] as $item) {
array_push($proxy, Clash::buildVmess($user->uuid, $item)); array_push($proxy, Clash::buildVmess($user->uuid, $item));
array_push($proxies, $item->name); array_push($proxies, $item->name);

View File

@ -8,6 +8,8 @@ use App\Utils\Clash;
use App\Utils\QuantumultX; use App\Utils\QuantumultX;
use App\Utils\Shadowrocket; use App\Utils\Shadowrocket;
use App\Utils\Surge; use App\Utils\Surge;
use App\Utils\Surfboard;
use App\Utils\URLSchemes;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Models\Server; use App\Models\Server;
use App\Utils\Helper; use App\Utils\Helper;
@ -18,35 +20,38 @@ class ClientController extends Controller
{ {
public function subscribe(Request $request) public function subscribe(Request $request)
{ {
$flag = $request->input('flag')
?? (isset($_SERVER['HTTP_USER_AGENT'])
? $_SERVER['HTTP_USER_AGENT']
: '');
$flag = strtolower($flag);
$user = $request->user; $user = $request->user;
// account not expired and is not banned. // account not expired and is not banned.
$userService = new UserService(); $userService = new UserService();
if ($userService->isAvailable($user)) { if ($userService->isAvailable($user)) {
$serverService = new ServerService(); $serverService = new ServerService();
$servers = $serverService->getAllServers($user); $servers = $serverService->getAllServers($user);
if ($flag) {
if (isset($_SERVER['HTTP_USER_AGENT'])) { if (strpos($flag, 'quantumult%20x') !== false) {
$_SERVER['HTTP_USER_AGENT'] = strtolower($_SERVER['HTTP_USER_AGENT']); die($this->quantumultX($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
if (strpos($_SERVER['HTTP_USER_AGENT'], 'quantumult%20x') !== false) {
die($this->quantumultX($user, $servers['vmess'], $servers['trojan']));
} }
if (strpos($_SERVER['HTTP_USER_AGENT'], 'quantumult') !== false) { if (strpos($flag, 'quantumult') !== false) {
die($this->quantumult($user, $servers['vmess'])); die($this->quantumult($user, $servers['vmess']));
} }
if (strpos($_SERVER['HTTP_USER_AGENT'], 'clash') !== false) { if (strpos($flag, 'clash') !== false) {
die($this->clash($user, $servers['vmess'], $servers['trojan'])); die($this->clash($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
} }
if (strpos($_SERVER['HTTP_USER_AGENT'], 'surfboard') !== false) { if (strpos($flag, 'surfboard') !== false) {
die($this->surfboard($user, $servers['vmess'])); die($this->surfboard($user, $servers['shadowsocks'], $servers['vmess']));
} }
if (strpos($_SERVER['HTTP_USER_AGENT'], 'surge') !== false) { if (strpos($flag, 'surge') !== false) {
die($this->surge($user, $servers['vmess'], $servers['trojan'])); die($this->surge($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
} }
if (strpos($_SERVER['HTTP_USER_AGENT'], 'shadowrocket') !== false) { if (strpos($flag, 'shadowrocket') !== false) {
die($this->shadowrocket($user, $servers['vmess'], $servers['trojan'])); die($this->shadowrocket($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
} }
} }
die($this->origin($user, $servers['vmess'], $servers['trojan'])); die($this->origin($user, $servers['shadowsocks'], $servers['vmess'], $servers['trojan']));
} }
} }
// TODO: Ready to stop support // TODO: Ready to stop support
@ -70,7 +75,7 @@ class ClientController extends Controller
return base64_encode($uri); return base64_encode($uri);
} }
private function shadowrocket($user, $vmess = [], $trojan = []) private function shadowrocket($user, $shadowsocks = [], $vmess = [], $trojan = [])
{ {
$uri = ''; $uri = '';
//display remaining traffic and expire date //display remaining traffic and expire date
@ -79,6 +84,9 @@ class ClientController extends Controller
$totalTraffic = round($user->transfer_enable / (1024*1024*1024), 2); $totalTraffic = round($user->transfer_enable / (1024*1024*1024), 2);
$expiredDate = date('Y-m-d', $user->expired_at); $expiredDate = date('Y-m-d', $user->expired_at);
$uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n"; $uri .= "STATUS=🚀↑:{$upload}GB,↓:{$download}GB,TOT:{$totalTraffic}GB💡Expires:{$expiredDate}\r\n";
foreach ($shadowsocks as $item) {
$uri .= Shadowrocket::buildShadowsocks($user->uuid, $item);
}
foreach ($vmess as $item) { foreach ($vmess as $item) {
$uri .= Shadowrocket::buildVmess($user->uuid, $item); $uri .= Shadowrocket::buildVmess($user->uuid, $item);
} }
@ -88,10 +96,13 @@ class ClientController extends Controller
return base64_encode($uri); return base64_encode($uri);
} }
private function quantumultX($user, $vmess = [], $trojan = []) private function quantumultX($user, $shadowsocks = [], $vmess = [], $trojan = [])
{ {
$uri = ''; $uri = '';
header("subscription-userinfo: upload={$user->u}; download={$user->d}; total={$user->transfer_enable}; expire={$user->expired_at}"); header("subscription-userinfo: upload={$user->u}; download={$user->d}; total={$user->transfer_enable}; expire={$user->expired_at}");
foreach ($shadowsocks as $item) {
$uri .= QuantumultX::buildShadowsocks($user->uuid, $item);
}
foreach ($vmess as $item) { foreach ($vmess as $item) {
$uri .= QuantumultX::buildVmess($user->uuid, $item); $uri .= QuantumultX::buildVmess($user->uuid, $item);
} }
@ -101,22 +112,33 @@ class ClientController extends Controller
return base64_encode($uri); return base64_encode($uri);
} }
private function origin($user, $vmess = [], $trojan = []) private function origin($user, $shadowsocks = [], $vmess = [], $trojan = [])
{ {
$uri = ''; $uri = '';
foreach ($shadowsocks as $item) {
$uri .= URLSchemes::buildShadowsocks($item, $user);
}
foreach ($vmess as $item) { foreach ($vmess as $item) {
$uri .= Helper::buildVmessLink($item, $user); $uri .= URLSchemes::buildVmess($item, $user);
} }
foreach ($trojan as $item) { foreach ($trojan as $item) {
$uri .= Helper::buildTrojanLink($item, $user); $uri .= URLSchemes::buildTrojan($item, $user);
} }
return base64_encode($uri); return base64_encode($uri);
} }
private function surge($user, $vmess = [], $trojan = []) private function surge($user, $shadowsocks = [], $vmess = [], $trojan = [])
{ {
$proxies = ''; $proxies = '';
$proxyGroup = ''; $proxyGroup = '';
foreach ($shadowsocks as $item) {
// [Proxy]
$proxies .= Surge::buildShadowsocks($user->uuid, $item);
// [Proxy Group]
$proxyGroup .= $item->name . ', ';
}
foreach ($vmess as $item) { foreach ($vmess as $item) {
// [Proxy] // [Proxy]
$proxies .= Surge::buildVmess($user->uuid, $item); $proxies .= Surge::buildVmess($user->uuid, $item);
@ -148,29 +170,21 @@ class ClientController extends Controller
return $config; return $config;
} }
private function surfboard($user, $vmess = []) private function surfboard($user, $shadowsocks = [], $vmess = [])
{ {
$proxies = ''; $proxies = '';
$proxyGroup = ''; $proxyGroup = '';
foreach ($shadowsocks as $item) {
// [Proxy]
$proxies .= Surfboard::buildShadowsocks($user->uuid, $item);
// [Proxy Group]
$proxyGroup .= $item->name . ', ';
}
foreach ($vmess as $item) { foreach ($vmess as $item) {
// [Proxy] // [Proxy]
$proxies .= $item->name . ' = vmess, ' . $item->host . ', ' . $item->port . ', username=' . $user->uuid; $proxies .= Surfboard::buildVmess($user->uuid, $item);
if ($item->tls) {
$tlsSettings = json_decode($item->tlsSettings);
$proxies .= ', tls=' . ($item->tls ? "true" : "false");
if (isset($tlsSettings->allowInsecure)) {
$proxies .= ', skip-cert-verify=' . ($tlsSettings->allowInsecure ? "true" : "false");
}
}
if ($item->network == 'ws') {
$proxies .= ', ws=true';
if ($item->networkSettings) {
$wsSettings = json_decode($item->networkSettings);
if (isset($wsSettings->path)) $proxies .= ', ws-path=' . $wsSettings->path;
if (isset($wsSettings->headers->Host)) $proxies .= ', ws-headers=host:' . $wsSettings->headers->Host;
}
}
$proxies .= "\r\n";
// [Proxy Group] // [Proxy Group]
$proxyGroup .= $item->name . ', '; $proxyGroup .= $item->name . ', ';
} }
@ -192,7 +206,7 @@ class ClientController extends Controller
return $config; return $config;
} }
private function clash($user, $vmess = [], $trojan = []) private function clash($user, $shadowsocks = [], $vmess = [], $trojan = [])
{ {
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml'; $defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.clash.yaml'; $customConfig = base_path() . '/resources/rules/custom.clash.yaml';
@ -203,12 +217,17 @@ class ClientController extends Controller
} }
$proxy = []; $proxy = [];
$proxies = []; $proxies = [];
foreach ($shadowsocks as $item) {
array_push($proxy, Clash::buildShadowsocks($user->uuid, $item));
array_push($proxies, $item->name);
}
foreach ($vmess as $item) { foreach ($vmess as $item) {
array_push($proxy, Clash::buildVmess($user->uuid, $item)); array_push($proxy, Clash::buildVmess($user->uuid, $item));
array_push($proxies, $item->name); array_push($proxies, $item->name);
} }
foreach ($trojan as $item) { foreach ($trojan as $item) {
array_push($proxy, Clash::buildTrojan($user->uuid, $item)); array_push($proxy, Clash::buildTrojan($user->uuid, $item));
array_push($proxies, $item->name); array_push($proxies, $item->name);
@ -216,6 +235,7 @@ class ClientController extends Controller
$config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy); $config['proxies'] = array_merge($config['proxies'] ? $config['proxies'] : [], $proxy);
foreach ($config['proxy-groups'] as $k => $v) { foreach ($config['proxy-groups'] as $k => $v) {
if (!is_array($config['proxy-groups'][$k]['proxies'])) continue;
$config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies); $config['proxy-groups'][$k]['proxies'] = array_merge($config['proxy-groups'][$k]['proxies'], $proxies);
} }
$yaml = Yaml::dump($config); $yaml = Yaml::dump($config);

View File

@ -157,6 +157,7 @@ class OrderController extends Controller
private function handle($tradeNo, $callbackNo) private function handle($tradeNo, $callbackNo)
{ {
$order = Order::where('trade_no', $tradeNo)->first(); $order = Order::where('trade_no', $tradeNo)->first();
if ($order->status === 1) return true;
if (!$order) { if (!$order) {
abort(500, 'order is not found'); abort(500, 'order is not found');
} }

View File

@ -73,7 +73,7 @@ class TelegramController extends Controller
$obj->args = array_slice($text, 1); $obj->args = array_slice($text, 1);
$obj->chat_id = $data['message']['chat']['id']; $obj->chat_id = $data['message']['chat']['id'];
$obj->message_id = $data['message']['message_id']; $obj->message_id = $data['message']['message_id'];
$obj->message_type = !isset($data['message']['reply_to_message']) ? 'send' : 'reply'; $obj->message_type = !isset($data['message']['reply_to_message']['text']) ? 'send' : 'reply';
$obj->text = $data['message']['text']; $obj->text = $data['message']['text'];
if ($obj->message_type === 'reply') { if ($obj->message_type === 'reply') {
$obj->reply_text = $data['message']['reply_to_message']['text']; $obj->reply_text = $data['message']['reply_to_message']['text'];
@ -184,7 +184,7 @@ class TelegramController extends Controller
abort(500, '用户不存在'); abort(500, '用户不存在');
} }
$ticketService = new TicketService(); $ticketService = new TicketService();
if ($user->is_admin) { if ($user->is_admin || $user->is_staff) {
$ticketService->replyByAdmin( $ticketService->replyByAdmin(
$ticketId, $ticketId,
$msg->text, $msg->text,
@ -194,4 +194,6 @@ class TelegramController extends Controller
$telegramService = new TelegramService(); $telegramService = new TelegramService();
$telegramService->sendMessage($msg->chat_id, "#`{$ticketId}` 的工单已回复成功", 'markdown'); $telegramService->sendMessage($msg->chat_id, "#`{$ticketId}` 的工单已回复成功", 'markdown');
} }
} }

View File

@ -14,11 +14,19 @@ use App\Models\InviteCode;
use App\Utils\Helper; use App\Utils\Helper;
use App\Utils\Dict; use App\Utils\Dict;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use ReCaptcha\ReCaptcha;
class AuthController extends Controller class AuthController extends Controller
{ {
public function register(AuthRegister $request) public function register(AuthRegister $request)
{ {
if ((int)config('v2board.recaptcha_enable', 0)) {
$recaptcha = new ReCaptcha(config('v2board.recaptcha_key'));
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
if (!$recaptchaResp->isSuccess()) {
abort(500, '验证码有误');
}
}
if ((int)config('v2board.email_whitelist_enable', 0)) { if ((int)config('v2board.email_whitelist_enable', 0)) {
if (!Helper::emailSuffixVerify( if (!Helper::emailSuffixVerify(
$request->input('email'), $request->input('email'),
@ -131,12 +139,15 @@ class AuthController extends Controller
$request->session()->put('is_admin', true); $request->session()->put('is_admin', true);
$data['is_admin'] = true; $data['is_admin'] = true;
} }
if ($user->is_staff) {
$request->session()->put('is_staff', true);
$data['is_staff'] = true;
}
return response([ return response([
'data' => $data 'data' => $data
]); ]);
} }
// 准备废弃
public function token2Login(Request $request) public function token2Login(Request $request)
{ {
if ($request->input('token')) { if ($request->input('token')) {
@ -146,7 +157,7 @@ class AuthController extends Controller
} else { } else {
$location = url($redirect); $location = url($redirect);
} }
return header('Location:' . $location); return redirect()->to($location)->send();
} }
if ($request->input('verify')) { if ($request->input('verify')) {
@ -178,7 +189,7 @@ class AuthController extends Controller
{ {
$user = User::where('token', $request->input('token'))->first(); $user = User::where('token', $request->input('token'))->first();
if (!$user) { if (!$user) {
abort(500, '用户不存在'); abort(500, '令牌有误');
} }
$code = Helper::guid(); $code = Helper::guid();
@ -189,6 +200,27 @@ class AuthController extends Controller
]); ]);
} }
public function getQuickLoginUrl(Request $request)
{
$user = User::where('token', $request->input('token'))->first();
if (!$user) {
abort(500, '令牌有误');
}
$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 (config('v2board.app_url')) {
$url = config('v2board.app_url') . $redirect;
} else {
$url = url($redirect);
}
return response([
'data' => $url
]);
}
public function check(Request $request) public function check(Request $request)
{ {
$data = [ $data = [

View File

@ -13,6 +13,7 @@ use App\Jobs\SendEmailJob;
use App\Models\InviteCode; use App\Models\InviteCode;
use App\Utils\Dict; use App\Utils\Dict;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use ReCaptcha\ReCaptcha;
class CommController extends Controller class CommController extends Controller
{ {
@ -24,7 +25,10 @@ class CommController extends Controller
'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0, 'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0,
'emailWhitelistSuffix' => (int)config('v2board.email_whitelist_enable', 0) 'emailWhitelistSuffix' => (int)config('v2board.email_whitelist_enable', 0)
? $this->getEmailSuffix() ? $this->getEmailSuffix()
: 0 : 0,
'isRecaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
'recaptchaSiteKey' => config('v2board.recaptcha_site_key'),
'appDescription' => config('v2board.app_description')
] ]
]); ]);
} }
@ -38,6 +42,13 @@ class CommController extends Controller
public function sendEmailVerify(CommSendEmailVerify $request) public function sendEmailVerify(CommSendEmailVerify $request)
{ {
if ((int)config('v2board.recaptcha_enable', 0)) {
$recaptcha = new ReCaptcha(config('v2board.recaptcha_key'));
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
if (!$recaptchaResp->isSuccess()) {
abort(500, '验证码有误');
}
}
$email = $request->input('email'); $email = $request->input('email');
if (Cache::get(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email))) { if (Cache::get(CacheKey::get('LAST_SEND_EMAIL_VERIFY_TIMESTAMP', $email))) {
abort(500, '验证码已发送,请过一会再请求'); abort(500, '验证码已发送,请过一会再请求');

View File

@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\Server;
use App\Models\ServerShadowsocks;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\ServerLog;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
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 !== config('v2board.server_token')) {
abort(500, 'token is error');
}
}
// 后端获取用户
public function user(Request $request)
{
$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(json_decode($server->group_id));
$result = [];
foreach ($users as $user) {
array_push($result, [
'id' => $user->id,
'port' => $server->server_port,
'cipher' => $server->cipher,
'secret' => $user->uuid
]);
}
return response([
'data' => $result
]);
}
// 后端提交数据
public function submit(Request $request)
{
// Log::info('serverSubmitData:' . $request->input('node_id') . ':' . file_get_contents('php://input'));
$server = ServerShadowsocks::find($request->input('node_id'));
if (!$server) {
return response([
'ret' => 0,
'msg' => 'server is not found'
]);
}
$data = file_get_contents('php://input');
$data = json_decode($data, true);
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_ONLINE_USER', $server->id), count($data), 3600);
$serverService = new ServerService();
$userService = new UserService();
DB::beginTransaction();
foreach ($data as $item) {
$u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate;
if (!$userService->trafficFetch((float)$u, (float)$d, (int)$item['user_id'])) {
DB::rollBack();
return response([
'ret' => 0,
'msg' => 'user fetch fail'
]);
}
$serverService->log(
$item['user_id'],
$request->input('node_id'),
$item['u'],
$item['d'],
$server->rate,
'shadowsocks'
);
}
DB::commit();
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, '参数错误');
}
$serverService = new ServerService();
try {
$json = $serverService->getTrojanConfig($nodeId, $localPort);
} catch (\Exception $e) {
abort(500, $e->getMessage());
}
die(json_encode($json, JSON_UNESCAPED_UNICODE));
}
}

View File

@ -74,10 +74,12 @@ class TrojanTidalabController extends Controller
Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600); Cache::put(CacheKey::get('SERVER_TROJAN_ONLINE_USER', $server->id), count($data), 3600);
$serverService = new ServerService(); $serverService = new ServerService();
$userService = new UserService(); $userService = new UserService();
DB::beginTransaction();
foreach ($data as $item) { foreach ($data as $item) {
$u = $item['u'] * $server->rate; $u = $item['u'] * $server->rate;
$d = $item['d'] * $server->rate; $d = $item['d'] * $server->rate;
if (!$userService->trafficFetch($u, $d, $item['user_id'])) { if (!$userService->trafficFetch($u, $d, $item['user_id'])) {
DB::rollBack();
return response([ return response([
'ret' => 0, 'ret' => 0,
'msg' => 'user fetch fail' 'msg' => 'user fetch fail'
@ -93,6 +95,7 @@ class TrojanTidalabController extends Controller
'trojan' 'trojan'
); );
} }
DB::commit();
return response([ return response([
'ret' => 1, 'ret' => 1,

View File

@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers\Staff;
use App\Http\Requests\Admin\NoticeSave;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Notice;
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,41 @@
<?php
namespace App\Http\Controllers\Staff;
use App\Http\Requests\Admin\PlanSave;
use App\Http\Requests\Admin\PlanSort;
use App\Http\Requests\Admin\PlanUpdate;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\Order;
use App\Models\User;
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,92 @@
<?php
namespace App\Http\Controllers\Staff;
use App\Services\TicketService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Ticket;
use App\Models\TicketMessage;
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();
for ($i = 0; $i < count($res); $i++) {
if ($res[$i]['last_reply_user_id'] == $request->session()->get('id')) {
$res[$i]['reply_status'] = 0;
} else {
$res[$i]['reply_status'] = 1;
}
}
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->session()->get('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,102 @@
<?php
namespace App\Http\Controllers\Staff;
use App\Http\Requests\Admin\UserSendMail;
use App\Http\Requests\Staff\UserUpdate;
use App\Jobs\SendEmailJob;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\Plan;
class UserController extends Controller
{
public function getUserInfoById(Request $request)
{
if (empty($request->input('id'))) {
abort(500, '参数错误');
}
return response([
'data' => User::find($request->input('id'))
]);
}
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' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.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,54 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\UserService;
use Illuminate\Http\Request;
use App\Models\Knowledge;
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, '知识不存在');
$user = User::find($request->session()->get('id'));
$userService = new UserService();
$appleId = $userService->isAvailable($user) ? config('v2board.apple_id') : '没有有效订阅无法使用本站提供的AppleID';
$appleIdPassword = $userService->isAvailable($user) ? config('v2board.apple_id_password') : '没有有效订阅无法使用本站提供的AppleID';
$subscribeUrl = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
$knowledge['body'] = str_replace('{{siteName}}', config('v2board.app_name', 'V2Board'), $knowledge['body']);
$knowledge['body'] = str_replace('{{appleId}}', $appleId, $knowledge['body']);
$knowledge['body'] = str_replace('{{appleIdPassword}}', $appleIdPassword, $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
]);
}
$knowledges = Knowledge::select(['id', 'category', 'title', 'updated_at'])
->where('language', $request->input('language'))
->where('show', 1)
->orderBy('sort', 'ASC')
->get()
->groupBy('category');
return response([
'data' => $knowledges
]);
}
}

View File

@ -82,23 +82,18 @@ class OrderController extends Controller
} }
} }
if (!$plan->renew && $user->plan_id == $plan->id) { if (!$plan->renew && $user->plan_id == $plan->id && $request->input('cycle') !== 'reset_price') {
abort(500, '该订阅无法续费,请更换其他订阅'); abort(500, '该订阅无法续费,请更换其他订阅');
} }
if ($plan[$request->input('cycle')] === NULL) { if ($plan[$request->input('cycle')] === NULL) {
if ($request->input('cycle') === 'reset_price') {
abort(500, '该订阅当前不支持重置流量');
}
abort(500, '该订阅周期无法进行购买,请选择其他周期'); abort(500, '该订阅周期无法进行购买,请选择其他周期');
} }
if ($request->input('cycle') === 'reset_price' && !$user->plan_id) { if ($request->input('cycle') === 'reset_price') {
abort(500, '必须存在订阅才可以购买流量重置包'); if ($user->expired_at <= time() || !$user->plan_id) {
abort(500, '订阅已过期或无有效订阅,无法购买重置包');
} }
if ($request->input('cycle') === 'reset_price' && $user->expired_at <= time()) {
abort(500, '当前订阅已过期,无法购买重置包');
} }
DB::beginTransaction(); DB::beginTransaction();
@ -116,6 +111,7 @@ class OrderController extends Controller
DB::rollBack(); DB::rollBack();
abort(500, '优惠券使用失败'); abort(500, '优惠券使用失败');
} }
$order->coupon_id = $couponService->getId();
} }
$orderService->setVipDiscount($user); $orderService->setVipDiscount($user);

View File

@ -24,7 +24,7 @@ class ServerController extends Controller
if ($userService->isAvailable($user)) { if ($userService->isAvailable($user)) {
$serverService = new ServerService(); $serverService = new ServerService();
$servers = $serverService->getAllServers($user); $servers = $serverService->getAllServers($user);
$servers = array_merge($servers['vmess'], $servers['trojan']); $servers = array_merge($servers['shadowsocks'], $servers['vmess'], $servers['trojan']);
} }
return response([ return response([
'data' => $servers 'data' => $servers

View File

@ -152,6 +152,11 @@ class TicketController extends Controller
public function withdraw(TicketWithdraw $request) public function withdraw(TicketWithdraw $request)
{ {
$user = User::find($request->session()->get('id'));
$limit = config('v2board.commission_withdraw_limit', 100);
if ($limit > ($user->commission_balance / 100)) {
abort(500, "当前系统要求的提现门槛佣金需为{$limit}CNY");
}
DB::beginTransaction(); DB::beginTransaction();
$subject = '[提现申请]本工单由系统发出'; $subject = '[提现申请]本工单由系统发出';
$ticket = Ticket::create([ $ticket = Ticket::create([
@ -190,6 +195,6 @@ class TicketController extends Controller
private function sendNotify(Ticket $ticket, TicketMessage $ticketMessage) private function sendNotify(Ticket $ticket, TicketMessage $ticketMessage)
{ {
$telegramService = new TelegramService(); $telegramService = new TelegramService();
$telegramService->sendMessageWithAdmin("📮工单提醒 #{$ticket->id}\n———————————————\n主题:\n`{$ticket->subject}`\n内容:\n`{$ticketMessage->message}`"); $telegramService->sendMessageWithAdmin("📮工单提醒 #{$ticket->id}\n———————————————\n主题:\n`{$ticket->subject}`\n内容:\n`{$ticketMessage->message}`", true);
} }
} }

View File

@ -1,82 +0,0 @@
<?php
namespace App\Http\Controllers\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Tutorial;
use Illuminate\Support\Facades\DB;
class TutorialController extends Controller
{
public function getSubscribeUrl(Request $request)
{
$user = User::find($request->session()->get('id'));
return response([
'data' => [
'subscribe_url' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token']
]
]);
}
public function getAppleID(Request $request)
{
$user = User::find($request->session()->get('id'));
if ($user->expired_at < time()) {
return response([
'data' => [
]
]);
}
return response([
'data' => [
'apple_id' => config('v2board.apple_id'),
'apple_id_password' => config('v2board.apple_id_password')
]
]);
}
public function fetch(Request $request)
{
if ($request->input('id')) {
$tutorial = Tutorial::where('show', 1)
->where('id', $request->input('id'))
->first();
if (!$tutorial) {
abort(500, '教程不存在');
}
return response([
'data' => $tutorial
]);
}
$tutorial = Tutorial::select(['id', 'category_id', 'title'])
->where('show', 1)
->orderBy('sort', 'ASC')
->get()
->groupBy('category_id');
$user = User::find($request->session()->get('id'));
$response = [
'data' => [
'tutorials' => $tutorial,
'safe_area_var' => [
'subscribe_url' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'],
'app_name' => config('v2board.app_name', 'V2board'),
'apple_id' => $user->expired_at > time() || $user->expired_at === NULL ? config('v2board.apple_id', '本站暂无提供AppleID信息') : '账号过期或未订阅',
'apple_id_password' => $user->expired_at > time() || $user->expired_at === NULL ? config('v2board.apple_id_password', '本站暂无提供AppleID信息') : '账号过期或未订阅'
]
]
];
// fuck support shadowrocket urlsafeb64 subscribe
$response['data']['safe_area_var']['b64_subscribe_url'] = str_replace(
array('+', '/', '='),
array('-', '_', ''),
base64_encode($response['data']['safe_area_var']['subscribe_url'])
);
// end
// fuck support surge urlencode subscribe
$response['data']['safe_area_var']['ue_subscribe_url'] = urlencode($response['data']['safe_area_var']['subscribe_url']);
// end
return response($response);
}
}

View File

@ -54,7 +54,6 @@ class UserController extends Controller
'last_login_at', 'last_login_at',
'created_at', 'created_at',
'banned', 'banned',
'is_admin',
'remind_expire', 'remind_expire',
'remind_traffic', 'remind_traffic',
'expired_at', 'expired_at',

View File

@ -68,7 +68,7 @@ class Kernel extends HttpKernel
'user' => \App\Http\Middleware\User::class, 'user' => \App\Http\Middleware\User::class,
'admin' => \App\Http\Middleware\Admin::class, 'admin' => \App\Http\Middleware\Admin::class,
'client' => \App\Http\Middleware\Client::class, 'client' => \App\Http\Middleware\Client::class,
'server' => \App\Http\Middleware\Server::class, 'staff' => \App\Http\Middleware\Staff::class,
]; ];
/** /**

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Middleware;
use Closure;
class Staff
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (!$request->session()->get('is_staff')) {
abort(403, '权限不足');
}
return $next($request);
}
}

View File

@ -22,6 +22,7 @@ class ConfigSave extends FormRequest
'invite_never_expire' => 'in:0,1', 'invite_never_expire' => 'in:0,1',
'commission_first_time_enable' => 'in:0,1', 'commission_first_time_enable' => 'in:0,1',
'commission_auto_check_enable' => 'in:0,1', 'commission_auto_check_enable' => 'in:0,1',
'commission_withdraw_limit' => 'nullable|numeric',
// site // site
'stop_register' => 'in:0,1', 'stop_register' => 'in:0,1',
'email_verify' => 'in:0,1', 'email_verify' => 'in:0,1',
@ -35,10 +36,14 @@ class ConfigSave extends FormRequest
'email_whitelist_enable' => 'in:0,1', 'email_whitelist_enable' => 'in:0,1',
'email_whitelist_suffix' => '', 'email_whitelist_suffix' => '',
'email_gmail_limit_enable' => 'in:0,1', 'email_gmail_limit_enable' => 'in:0,1',
'recaptcha_enable' => 'in:0,1',
'recaptcha_key' => '',
'recaptcha_site_key' => '',
// subscribe // subscribe
'plan_change_enable' => 'in:0,1', 'plan_change_enable' => 'in:0,1',
'reset_traffic_method' => 'in:0,1', 'reset_traffic_method' => 'in:0,1',
'renew_reset_traffic_enable' => 'in:0,1', 'renew_reset_traffic_enable' => 'in:0,1',
'surplus_enable' => 'in:0,1',
// server // server
'server_token' => 'nullable|min:16', 'server_token' => 'nullable|min:16',
'server_license' => 'nullable', 'server_license' => 'nullable',
@ -81,7 +86,7 @@ class ConfigSave extends FormRequest
'frontend_background_url' => 'nullable|url', 'frontend_background_url' => 'nullable|url',
'frontend_admin_path' => '', 'frontend_admin_path' => '',
// tutorial // tutorial
'apple_id' => 'email', 'apple_id' => 'nullable|email',
'apple_id_password' => '', 'apple_id_password' => '',
// email // email
'email_template' => '', 'email_template' => '',

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class CouponGenerate extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'generate_count' => 'nullable|integer|max:500',
'name' => 'required',
'type' => 'required|in:1,2',
'value' => 'required|integer',
'started_at' => 'required|integer',
'ended_at' => 'required|integer',
'limit_use' => 'nullable|integer',
'limit_plan_ids' => 'nullable|array',
'code' => ''
];
}
public function messages()
{
return [
'generate_count.integer' => '生成数量必须为数字',
'generate_count.max' => '生成数量最大为500个',
'name.required' => '名称不能为空',
'type.required' => '类型不能为空',
'type.in' => '类型格式有误',
'value.required' => '金额或比例不能为空',
'value.integer' => '金额或比例格式有误',
'started_at.required' => '开始时间不能为空',
'started_at.integer' => '开始时间格式有误',
'ended_at.required' => '结束时间不能为空',
'ended_at.integer' => '结束时间格式有误',
'limit_use.integer' => '使用次数格式有误',
'limit_plan_ids.array' => '指定订阅格式有误'
];
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class KnowledgeCategorySave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required',
'language' => 'required'
];
}
public function messages()
{
return [
'name.required' => '分类名称不能为空',
'language.required' => '分类语言不能为空'
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class KnowledgeCategorySort extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'knowledge_category_ids' => 'required|array'
];
}
public function messages()
{
return [
'knowledge_category_ids.required' => '分类不能为空',
'knowledge_category_ids.array' => '分类格式有误'
];
}
}

View File

@ -4,7 +4,7 @@ namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class TutorialSave extends FormRequest class KnowledgeSave extends FormRequest
{ {
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
@ -14,10 +14,10 @@ class TutorialSave extends FormRequest
public function rules() public function rules()
{ {
return [ return [
'category' => 'required',
'language' => 'required',
'title' => 'required', 'title' => 'required',
// 1:windows 2:macos 3:ios 4:android 5:linux 6:router 'body' => 'required'
'category_id' => 'required|in:1,2,3,4,5,6',
'steps' => 'required'
]; ];
} }
@ -25,9 +25,9 @@ class TutorialSave extends FormRequest
{ {
return [ return [
'title.required' => '标题不能为空', 'title.required' => '标题不能为空',
'category_id.required' => '分类不能为空', 'category.required' => '分类不能为空',
'category_id.in' => '分类格式不正确', 'body.required' => '内容不能为空',
'steps.required' => '教程步骤不能为空' 'language.required' => '语言不能为空'
]; ];
} }
} }

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class KnowledgeSort extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'knowledge_ids' => 'required|array'
];
}
public function messages()
{
return [
'knowledge_ids.required' => '知识ID不能为空',
'knowledge_ids.array' => '知识ID格式有误'
];
}
}

View File

@ -22,6 +22,8 @@ class PlanSave extends FormRequest
'quarter_price' => 'nullable|integer', 'quarter_price' => 'nullable|integer',
'half_year_price' => 'nullable|integer', 'half_year_price' => 'nullable|integer',
'year_price' => 'nullable|integer', 'year_price' => 'nullable|integer',
'two_year_price' => 'nullable|integer',
'three_year_price' => 'nullable|integer',
'onetime_price' => 'nullable|integer', 'onetime_price' => 'nullable|integer',
'reset_price' => 'nullable|integer' 'reset_price' => 'nullable|integer'
]; ];
@ -39,6 +41,8 @@ class PlanSave extends FormRequest
'quarter_price.integer' => '季付金额格式有误', 'quarter_price.integer' => '季付金额格式有误',
'half_year_price.integer' => '半年付金额格式有误', 'half_year_price.integer' => '半年付金额格式有误',
'year_price.integer' => '年付金额格式有误', 'year_price.integer' => '年付金额格式有误',
'two_year_price.integer' => '两年付金额格式有误',
'three_year_price.integer' => '三年付金额格式有误',
'onetime_price.integer' => '一次性金额有误', 'onetime_price.integer' => '一次性金额有误',
'reset_price.integer' => '流量重置包金额有误' 'reset_price.integer' => '流量重置包金额有误'
]; ];

View File

@ -0,0 +1,46 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class ServerShadowsocksSave extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'cipher' => 'required|in:aes-128-gcm,aes-256-gcm,chacha20-ietf-poly1305',
'tags' => 'nullable|array',
'rate' => 'required|numeric'
];
}
public function messages()
{
return [
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'parent_id.integer' => '父节点格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',
'server_port.required' => '后端服务端口不能为空',
'cipher.required' => '加密方式不能为空',
'tags.array' => '标签格式不正确',
'rate.required' => '倍率不能为空',
'rate.numeric' => '倍率格式不正确'
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class ServerShadowsocksSort extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'server_ids' => 'required|array'
];
}
public function messages()
{
return [
'server_ids.required' => '服务器ID不能为空',
'server_ids.array' => '服务器ID格式有误'
];
}
}

View File

@ -4,25 +4,25 @@ namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class TutorialSort extends FormRequest class ServerShadowsocksUpdate extends FormRequest
{ {
/** /**
* Get the validation rules that apply to the request. * Get the validation rules that apply to the request.
* *
* @return array * @return array
*/ */
public function rules() public function rules()
{ {
return [ return [
'tutorial_ids' => 'required|array' 'show' => 'in:0,1'
]; ];
} }
public function messages() public function messages()
{ {
return [ return [
'tutorial_ids.required' => '教程ID不能为空', 'show.in' => '显示状态格式不正确'
'tutorial_ids.array' => '教程ID格式有误'
]; ];
} }
} }

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class UserFetch extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'filter.*.key' => 'required|in:id,email,transfer_enable,d,expired_at,uuid,token,invite_by_email,invite_user_id',
'filter.*.condition' => 'required|in:>,<,=,>=,<=,模糊',
'filter.*.value' => 'required'
];
}
public function messages()
{
return [
];
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class UserGenerate extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'generate_count' => 'nullable|integer|max:500',
'expired_at' => 'nullable|integer',
'plan_id' => 'nullable|integer',
'email_prefix' => 'nullable',
'email_suffix' => 'required',
'password' => 'nullable'
];
}
public function messages()
{
return [
'generate_count.integer' => '生成数量必须为数字',
'generate_count.max' => '生成数量最大为500个'
];
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class UserSendMail extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'subject' => 'required',
'content' => 'required',
];
}
public function messages()
{
return [
'subject.required' => '主题不能为空',
'content.required' => '发送内容不能为空'
];
}
}

View File

@ -23,6 +23,7 @@ class UserUpdate extends FormRequest
'commission_rate' => 'nullable|integer|min:0|max:100', 'commission_rate' => 'nullable|integer|min:0|max:100',
'discount' => 'nullable|integer|min:0|max:100', 'discount' => 'nullable|integer|min:0|max:100',
'is_admin' => 'required|in:0,1', 'is_admin' => 'required|in:0,1',
'is_staff' => 'required|in:0,1',
'u' => 'integer', 'u' => 'integer',
'd' => 'integer', 'd' => 'integer',
'balance' => 'integer', 'balance' => 'integer',
@ -41,6 +42,8 @@ class UserUpdate extends FormRequest
'banned.in' => '是否封禁格式不正确', 'banned.in' => '是否封禁格式不正确',
'is_admin.required' => '是否管理员不能为空', 'is_admin.required' => '是否管理员不能为空',
'is_admin.in' => '是否管理员格式不正确', 'is_admin.in' => '是否管理员格式不正确',
'is_staff.required' => '是否员工不能为空',
'is_staff.in' => '是否员工格式不正确',
'plan_id.integer' => '订阅计划格式不正确', 'plan_id.integer' => '订阅计划格式不正确',
'commission_rate.integer' => '推荐返利比例格式不正确', 'commission_rate.integer' => '推荐返利比例格式不正确',
'commission_rate.nullable' => '推荐返利比例格式不正确', 'commission_rate.nullable' => '推荐返利比例格式不正确',

View File

@ -0,0 +1,56 @@
<?php
namespace App\Http\Requests\Staff;
use Illuminate\Foundation\Http\FormRequest;
class UserUpdate extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required|email',
'password' => 'nullable',
'transfer_enable' => 'numeric',
'expired_at' => 'nullable|integer',
'banned' => 'required|in:0,1',
'plan_id' => 'nullable|integer',
'commission_rate' => 'nullable|integer|min:0|max:100',
'discount' => 'nullable|integer|min:0|max:100',
'u' => 'integer',
'd' => 'integer',
'balance' => 'integer',
'commission_balance' => 'integer'
];
}
public function messages()
{
return [
'email.required' => '邮箱不能为空',
'email.email' => '邮箱格式不正确',
'transfer_enable.numeric' => '流量格式不正确',
'expired_at.integer' => '到期时间格式不正确',
'banned.required' => '是否封禁不能为空',
'banned.in' => '是否封禁格式不正确',
'plan_id.integer' => '订阅计划格式不正确',
'commission_rate.integer' => '推荐返利比例格式不正确',
'commission_rate.nullable' => '推荐返利比例格式不正确',
'commission_rate.min' => '推荐返利比例最小为0',
'commission_rate.max' => '推荐返利比例最大为100',
'discount.integer' => '专属折扣比例格式不正确',
'discount.nullable' => '专属折扣比例格式不正确',
'discount.min' => '专属折扣比例最小为0',
'discount.max' => '专属折扣比例最大为100',
'u.integer' => '上行流量格式不正确',
'd.integer' => '下行流量格式不正确',
'balance.integer' => '余额格式不正确',
'commission_balance.integer' => '佣金格式不正确'
];
}
}

View File

@ -15,7 +15,7 @@ class OrderSave extends FormRequest
{ {
return [ return [
'plan_id' => 'required', 'plan_id' => 'required',
'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,onetime_price,reset_price' 'cycle' => 'required|in:month_price,quarter_price,half_year_price,year_price,two_year_price,three_year_price,onetime_price,reset_price'
]; ];
} }

View File

@ -48,6 +48,16 @@ class AdminRoute
$router->post('sort', 'Admin\\Server\\V2rayController@sort'); $router->post('sort', 'Admin\\Server\\V2rayController@sort');
$router->post('viewConfig', 'Admin\\Server\\V2rayController@viewConfig'); $router->post('viewConfig', 'Admin\\Server\\V2rayController@viewConfig');
}); });
$router->group([
'prefix' => 'server/shadowsocks'
], function ($router) {
$router->get ('fetch', 'Admin\\Server\\ShadowsocksController@fetch');
$router->post('save', 'Admin\\Server\\ShadowsocksController@save');
$router->post('drop', 'Admin\\Server\\ShadowsocksController@drop');
$router->post('update', 'Admin\\Server\\ShadowsocksController@update');
$router->post('copy', 'Admin\\Server\\ShadowsocksController@copy');
$router->post('sort', 'Admin\\Server\\ShadowsocksController@sort');
});
// Order // Order
$router->get ('/order/fetch', 'Admin\\OrderController@fetch'); $router->get ('/order/fetch', 'Admin\\OrderController@fetch');
$router->post('/order/repair', 'Admin\\OrderController@repair'); $router->post('/order/repair', 'Admin\\OrderController@repair');
@ -57,6 +67,10 @@ class AdminRoute
$router->get ('/user/fetch', 'Admin\\UserController@fetch'); $router->get ('/user/fetch', 'Admin\\UserController@fetch');
$router->post('/user/update', 'Admin\\UserController@update'); $router->post('/user/update', 'Admin\\UserController@update');
$router->get ('/user/getUserInfoById', 'Admin\\UserController@getUserInfoById'); $router->get ('/user/getUserInfoById', 'Admin\\UserController@getUserInfoById');
$router->post('/user/generate', 'Admin\\UserController@generate');
$router->post('/user/dumpCSV', 'Admin\\UserController@dumpCSV');
$router->post('/user/sendMail', 'Admin\\UserController@sendMail');
$router->post('/user/ban', 'Admin\\UserController@ban');
// Stat // Stat
$router->get ('/stat/getOverride', 'Admin\\StatController@getOverride'); $router->get ('/stat/getOverride', 'Admin\\StatController@getOverride');
// Notice // Notice
@ -68,18 +82,17 @@ class AdminRoute
$router->get ('/ticket/fetch', 'Admin\\TicketController@fetch'); $router->get ('/ticket/fetch', 'Admin\\TicketController@fetch');
$router->post('/ticket/reply', 'Admin\\TicketController@reply'); $router->post('/ticket/reply', 'Admin\\TicketController@reply');
$router->post('/ticket/close', 'Admin\\TicketController@close'); $router->post('/ticket/close', 'Admin\\TicketController@close');
// Mail
$router->post('/mail/send', 'Admin\\MailController@send');
// Coupon // Coupon
$router->get ('/coupon/fetch', 'Admin\\CouponController@fetch'); $router->get ('/coupon/fetch', 'Admin\\CouponController@fetch');
$router->post('/coupon/save', 'Admin\\CouponController@save'); $router->post('/coupon/generate', 'Admin\\CouponController@generate');
$router->post('/coupon/drop', 'Admin\\CouponController@drop'); $router->post('/coupon/drop', 'Admin\\CouponController@drop');
// Tutorial // Knowledge
$router->get ('/tutorial/fetch', 'Admin\\TutorialController@fetch'); $router->get ('/knowledge/fetch', 'Admin\\KnowledgeController@fetch');
$router->post('/tutorial/save', 'Admin\\TutorialController@save'); $router->get ('/knowledge/getCategory', 'Admin\\KnowledgeController@getCategory');
$router->post('/tutorial/show', 'Admin\\TutorialController@show'); $router->post('/knowledge/save', 'Admin\\KnowledgeController@save');
$router->post('/tutorial/drop', 'Admin\\TutorialController@drop'); $router->post('/knowledge/show', 'Admin\\KnowledgeController@show');
$router->post('/tutorial/sort', 'Admin\\TutorialController@sort'); $router->post('/knowledge/drop', 'Admin\\KnowledgeController@drop');
$router->post('/knowledge/sort', 'Admin\\KnowledgeController@sort');
}); });
} }
} }

View File

@ -17,6 +17,7 @@ class PassportRoute
$router->get ('/auth/check', 'Passport\\AuthController@check'); $router->get ('/auth/check', 'Passport\\AuthController@check');
$router->post('/auth/forget', 'Passport\\AuthController@forget'); $router->post('/auth/forget', 'Passport\\AuthController@forget');
$router->post('/auth/getTempToken', 'Passport\\AuthController@getTempToken'); $router->post('/auth/getTempToken', 'Passport\\AuthController@getTempToken');
$router->post('/auth/getQuickLoginUrl', 'Passport\\AuthController@getQuickLoginUrl');
// Comm // Comm
$router->get ('/comm/config', 'Passport\\CommController@config'); $router->get ('/comm/config', 'Passport\\CommController@config');
$router->post('/comm/sendEmailVerify', 'Passport\\CommController@sendEmailVerify'); $router->post('/comm/sendEmailVerify', 'Passport\\CommController@sendEmailVerify');

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Routes;
use Illuminate\Contracts\Routing\Registrar;
class StaffRoute
{
public function map(Registrar $router)
{
$router->group([
'prefix' => 'staff',
'middleware' => 'staff'
], function ($router) {
// Ticket
$router->get ('/ticket/fetch', 'Staff\\TicketController@fetch');
$router->post('/ticket/reply', 'Staff\\TicketController@reply');
$router->post('/ticket/close', 'Staff\\TicketController@close');
// User
$router->post('/user/update', 'Staff\\UserController@update');
$router->get ('/user/getUserInfoById', 'Staff\\UserController@getUserInfoById');
$router->post('/user/sendMail', 'Staff\\UserController@sendMail');
$router->post('/user/ban', 'Staff\\UserController@ban');
// Plan
$router->get ('/plan/fetch', 'Staff\\PlanController@fetch');
// Notice
$router->get ('/notice/fetch', 'Admin\\NoticeController@fetch');
$router->post('/notice/save', 'Admin\\NoticeController@save');
$router->post('/notice/update', 'Admin\\NoticeController@update');
$router->post('/notice/drop', 'Admin\\NoticeController@drop');
});
}
}

View File

@ -34,10 +34,6 @@ class UserRoute
$router->get ('/invite/save', 'User\\InviteController@save'); $router->get ('/invite/save', 'User\\InviteController@save');
$router->get ('/invite/fetch', 'User\\InviteController@fetch'); $router->get ('/invite/fetch', 'User\\InviteController@fetch');
$router->get ('/invite/details', 'User\\InviteController@details'); $router->get ('/invite/details', 'User\\InviteController@details');
// Tutorial
$router->get ('/tutorial/getSubscribeUrl', 'User\\TutorialController@getSubscribeUrl');
$router->get ('/tutorial/getAppleID', 'User\\TutorialController@getAppleID');
$router->get ('/tutorial/fetch', 'User\\TutorialController@fetch');
// Notice // Notice
$router->get ('/notice/fetch', 'User\\NoticeController@fetch'); $router->get ('/notice/fetch', 'User\\NoticeController@fetch');
// Ticket // Ticket
@ -55,6 +51,9 @@ class UserRoute
$router->get ('/telegram/getBotInfo', 'User\\TelegramController@getBotInfo'); $router->get ('/telegram/getBotInfo', 'User\\TelegramController@getBotInfo');
// Comm // Comm
$router->get ('/comm/config', 'User\\CommController@config'); $router->get ('/comm/config', 'User\\CommController@config');
// Knowledge
$router->get ('/knowledge/fetch', 'User\\KnowledgeController@fetch');
$router->get ('/knowledge/getCategory', 'User\\KnowledgeController@getCategory');
}); });
} }
} }

12
app/Models/Knowledge.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Knowledge extends Model
{
protected $table = 'v2_knowledge';
protected $dateFormat = 'U';
protected $guarded = ['id'];
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ServerShadowsocks extends Model
{
protected $table = 'v2_server_shadowsocks';
protected $dateFormat = 'U';
protected $guarded = ['id'];
}

View File

@ -51,4 +51,9 @@ class CouponService
} }
return true; return true;
} }
public function getId()
{
return $this->coupon->id;
}
} }

View File

@ -2,6 +2,37 @@
namespace App\Services; namespace App\Services;
use App\Jobs\SendEmailJob;
use App\Models\User;
use App\Utils\CacheKey;
use Illuminate\Support\Facades\Cache;
class MailService class MailService
{ {
public function remindTraffic (User $user)
{
if (!$user->remind_traffic) return;
if (!$this->remindTrafficIsWarnValue(($user->u + $user->d), $user->transfer_enable)) return;
$flag = CacheKey::get('LAST_SEND_EMAIL_REMIND_TRAFFIC', $user->id);
if (Cache::get($flag)) return;
if (!Cache::put($flag, 1, 24 * 3600)) return;
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => '在' . config('v2board.app_name', 'V2board') . '的流量使用已达到80%',
'template_name' => 'remindTraffic',
'template_value' => [
'name' => config('v2board.app_name', 'V2Board'),
'url' => config('v2board.app_url')
]
]);
}
private function remindTrafficIsWarnValue($ud, $transfer_enable)
{
if ($ud <= 0) return false;
$percentage = $ud / $transfer_enable * 100;
if ($percentage < 80) return false;
if ($percentage >= 100) return false;
return true;
}
} }

View File

@ -9,6 +9,14 @@ use Illuminate\Support\Facades\DB;
class OrderService class OrderService
{ {
CONST STR_TO_TIME = [
'month_price' => 1,
'quarter_price' => 3,
'half_year_price' => 6,
'year_price' => 12,
'two_year_price' => 24,
'three_year_price' => 36
];
public $order; public $order;
public function __construct(Order $order) public function __construct(Order $order)
@ -16,6 +24,52 @@ class OrderService
$this->order = $order; $this->order = $order;
} }
public function open()
{
$order = $this->order;
$user = User::find($order->user_id);
$plan = Plan::find($order->plan_id);
if ($order->refund_amount) {
$user->balance = $user->balance + $order->refund_amount;
}
DB::beginTransaction();
if ($order->surplus_order_ids) {
try {
Order::whereIn('id', json_decode($order->surplus_order_ids))->update([
'status' => 4
]);
} catch (\Exception $e) {
DB::rollback();
abort(500, '开通失败');
}
}
switch ((string)$order->cycle) {
case 'onetime_price':
$this->buyByOneTime($user, $plan);
break;
case 'reset_price':
$this->buyByResetTraffic($user);
break;
default:
$this->buyByCycle($order, $user, $plan);
}
if ((int)config('v2board.renew_reset_traffic_enable', 0)) $this->buyByResetTraffic($user);
if (!$user->save()) {
DB::rollBack();
abort(500, '开通失败');
}
$order->status = 3;
if (!$order->save()) {
DB::rollBack();
abort(500, '开通失败');
}
DB::commit();
}
public function cancel():bool public function cancel():bool
{ {
$order = $this->order; $order = $this->order;
@ -36,20 +90,15 @@ class OrderService
return true; return true;
} }
public function create()
{
}
public function setOrderType(User $user) public function setOrderType(User $user)
{ {
$order = $this->order; $order = $this->order;
if ($order->cycle === 'reset_price') { if ($order->cycle === 'reset_price') {
$order->type = 4; $order->type = 4;
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id && $user->expired_at > time()) { // 用户订阅存在且用户订阅与购买订阅不同且用户订阅未过期 === 更换 } else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id && ($user->expired_at > time() || $user->expired_at === NULL)) {
if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系客服或提交工单操作'); if (!(int)config('v2board.plan_change_enable', 1)) abort(500, '目前不允许更改订阅,请联系客服或提交工单操作');
$order->type = 3; $order->type = 3;
$this->getSurplusValue($user, $order); if ((int)config('v2board.surplus_enable', 1)) $this->getSurplusValue($user, $order);
if ($order->surplus_amount >= $order->total_amount) { if ($order->surplus_amount >= $order->total_amount) {
$order->refund_amount = $order->surplus_amount - $order->total_amount; $order->refund_amount = $order->surplus_amount - $order->total_amount;
$order->total_amount = 0; $order->total_amount = 0;
@ -106,31 +155,35 @@ class OrderService
if ($user->discount && $trafficUnitPrice) { if ($user->discount && $trafficUnitPrice) {
$trafficUnitPrice = $trafficUnitPrice - ($trafficUnitPrice * $user->discount / 100); $trafficUnitPrice = $trafficUnitPrice - ($trafficUnitPrice * $user->discount / 100);
} }
$notUsedTrafficPrice = $plan->transfer_enable - (($user->u + $user->d) / 1073741824); $notUsedTraffic = $plan->transfer_enable - (($user->u + $user->d) / 1073741824);
$result = $trafficUnitPrice * $notUsedTrafficPrice; $result = $trafficUnitPrice * $notUsedTraffic;
$orderModel = Order::where('user_id', $user->id)->where('cycle', '!=', 'reset_price')->where('status', 3); $orderModel = Order::where('user_id', $user->id)->where('cycle', '!=', 'reset_price')->where('status', 3);
$order->surplus_amount = $result > 0 ? $result : 0; $order->surplus_amount = $result > 0 ? $result : 0;
$order->surplus_order_ids = json_encode(array_map(function ($v) { return $v['id'];}, $orderModel->get()->toArray())); $order->surplus_order_ids = json_encode(array_column($orderModel->get()->toArray(), 'id'));
}
private function orderIsUsed(Order $order):bool
{
$month = self::STR_TO_TIME[$order->cycle];
$orderExpireDay = strtotime('+' . $month . ' month', $order->created_at->timestamp);
if ($orderExpireDay < time()) return true;
return false;
} }
private function getSurplusValueByCycle(User $user, Order $order) private function getSurplusValueByCycle(User $user, Order $order)
{ {
$strToMonth = [
'month_price' => 1,
'quarter_price' => 3,
'half_year_price' => 6,
'year_price' => 12
];
$orderModel = Order::where('user_id', $user->id) $orderModel = Order::where('user_id', $user->id)
->where('cycle', '!=', 'reset_price') ->where('cycle', '!=', 'reset_price')
->where('status', 3); ->where('status', 3);
$orders = $orderModel->get();
$orderSurplusMonth = 0; $orderSurplusMonth = 0;
$orderSurplusAmount = 0; $orderSurplusAmount = 0;
$userSurplusMonth = ($user->expired_at - time()) / 2678400; $userSurplusMonth = ($user->expired_at - time()) / 2678400;
foreach ($orderModel->get() as $item) { foreach ($orders as $k => $item) {
// 兼容历史余留问题 // 兼容历史余留问题
if ($item->cycle === 'onetime_price') continue; if ($item->cycle === 'onetime_price') continue;
$orderSurplusMonth = $orderSurplusMonth + $strToMonth[$item->cycle]; if ($this->orderIsUsed($item)) continue;
$orderSurplusMonth = $orderSurplusMonth + self::STR_TO_TIME[$item->cycle];
$orderSurplusAmount = $orderSurplusAmount + ($item['total_amount'] + $item['balance_amount']); $orderSurplusAmount = $orderSurplusAmount + ($item['total_amount'] + $item['balance_amount']);
} }
if (!$orderSurplusMonth || !$orderSurplusAmount) return; if (!$orderSurplusMonth || !$orderSurplusAmount) return;
@ -145,7 +198,7 @@ class OrderService
return; return;
} }
$order->surplus_amount = $orderSurplusAmount > 0 ? $orderSurplusAmount : 0; $order->surplus_amount = $orderSurplusAmount > 0 ? $orderSurplusAmount : 0;
$order->surplus_order_ids = json_encode(array_map(function ($v) { return $v['id'];}, $orderModel->get()->toArray())); $order->surplus_order_ids = json_encode(array_column($orders->toArray(), 'id'));
} }
public function success(string $callbackNo) public function success(string $callbackNo)
@ -158,4 +211,57 @@ class OrderService
$order->callback_no = $callbackNo; $order->callback_no = $callbackNo;
return $order->save(); return $order->save();
} }
private function buyByResetTraffic(User $user)
{
$user->u = 0;
$user->d = 0;
}
private function buyByCycle(Order $order, User $user, Plan $plan)
{
// change plan process
if ((int)$order->type === 3) {
$user->expired_at = time();
}
$user->transfer_enable = $plan->transfer_enable * 1073741824;
// 从一次性转换到循环
if ($user->expired_at === NULL) $this->buyByResetTraffic($user);
// 新购
if ($order->type === 1) $this->buyByResetTraffic($user);
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = $this->getTime($order->cycle, $user->expired_at);
}
private function buyByOneTime(User $user, Plan $plan)
{
$this->buyByResetTraffic($user);
$user->transfer_enable = $plan->transfer_enable * 1073741824;
$user->plan_id = $plan->id;
$user->group_id = $plan->group_id;
$user->expired_at = NULL;
}
private function getTime($str, $timestamp)
{
if ($timestamp < time()) {
$timestamp = time();
}
switch ($str) {
case 'month_price':
return strtotime('+1 month', $timestamp);
case 'quarter_price':
return strtotime('+3 month', $timestamp);
case 'half_year_price':
return strtotime('+6 month', $timestamp);
case 'year_price':
return strtotime('+12 month', $timestamp);
case 'two_year_price':
return strtotime('+24 month', $timestamp);
case 'three_year_price':
return strtotime('+36 month', $timestamp);
}
}
} }

View File

@ -3,11 +3,13 @@
namespace App\Services; namespace App\Services;
use App\Models\ServerLog; use App\Models\ServerLog;
use App\Models\ServerShadowsocks;
use App\Models\User; use App\Models\User;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerTrojan; use App\Models\ServerTrojan;
use App\Utils\CacheKey; use App\Utils\CacheKey;
use App\Utils\Helper; use App\Utils\Helper;
use App\Utils\URLSchemes;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
class ServerService class ServerService
@ -24,9 +26,10 @@ class ServerService
} }
$vmesss = $model->get(); $vmesss = $model->get();
foreach ($vmesss as $k => $v) { foreach ($vmesss as $k => $v) {
$vmesss[$k]['protocol_type'] = 'vmess';
$groupId = json_decode($vmesss[$k]['group_id']); $groupId = json_decode($vmesss[$k]['group_id']);
if (in_array($user->group_id, $groupId)) { if (in_array($user->group_id, $groupId)) {
$vmesss[$k]['link'] = Helper::buildVmessLink($vmesss[$k], $user); $vmesss[$k]['link'] = URLSchemes::buildVmess($vmesss[$k], $user);
if ($vmesss[$k]['parent_id']) { if ($vmesss[$k]['parent_id']) {
$vmesss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $vmesss[$k]['parent_id'])); $vmesss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_V2RAY_LAST_CHECK_AT', $vmesss[$k]['parent_id']));
} else { } else {
@ -49,7 +52,9 @@ class ServerService
} }
$trojans = $model->get(); $trojans = $model->get();
foreach ($trojans as $k => $v) { foreach ($trojans as $k => $v) {
$trojans[$k]['protocol_type'] = 'trojan';
$groupId = json_decode($trojans[$k]['group_id']); $groupId = json_decode($trojans[$k]['group_id']);
$trojans[$k]['link'] = URLSchemes::buildTrojan($trojans[$k], $user);
if (in_array($user->group_id, $groupId)) { if (in_array($user->group_id, $groupId)) {
if ($trojans[$k]['parent_id']) { if ($trojans[$k]['parent_id']) {
$trojans[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojans[$k]['parent_id'])); $trojans[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $trojans[$k]['parent_id']));
@ -63,9 +68,35 @@ class ServerService
return $trojan; return $trojan;
} }
public function getShadowsocks(User $user, $all = false)
{
$shadowsocks = [];
$model = ServerShadowsocks::orderBy('sort', 'ASC');
if (!$all) {
$model->where('show', 1);
}
$shadowsockss = $model->get();
foreach ($shadowsockss as $k => $v) {
$shadowsockss[$k]['protocol_type'] = 'shadowsocks';
$groupId = json_decode($shadowsockss[$k]['group_id']);
$shadowsockss[$k]['link'] = URLSchemes::buildShadowsocks($shadowsockss[$k], $user);
if (in_array($user->group_id, $groupId)) {
if ($shadowsockss[$k]['parent_id']) {
$shadowsockss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsockss[$k]['parent_id']));
} else {
$shadowsockss[$k]['last_check_at'] = Cache::get(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $shadowsockss[$k]['id']));
}
array_push($shadowsocks, $shadowsockss[$k]);
}
}
return $shadowsocks;
}
public function getAllServers(User $user, $all = false) public function getAllServers(User $user, $all = false)
{ {
return [ return [
'shadowsocks' => $this->getShadowsocks($user, $all),
'vmess' => $this->getVmess($user, $all), 'vmess' => $this->getVmess($user, $all),
'trojan' => $this->getTrojan($user, $all) 'trojan' => $this->getTrojan($user, $all)
]; ];

View File

@ -46,10 +46,15 @@ class TelegramService {
return $response; return $response;
} }
public function sendMessageWithAdmin($message) public function sendMessageWithAdmin($message, $isStaff = false)
{ {
if (!config('v2board.telegram_bot_enable', 0)) return; if (!config('v2board.telegram_bot_enable', 0)) return;
$users = User::where('is_admin', 1) $users = User::where(function ($query) use ($isStaff) {
$query->where('is_admin', 1);
if ($isStaff) {
$query->orWhere('is_staff', 1);
}
})
->where('telegram_id', '!=', NULL) ->where('telegram_id', '!=', NULL)
->get(); ->get();
foreach ($users as $user) { foreach ($users as $user) {

View File

@ -80,7 +80,7 @@ class UserService
{ {
$user = User::find($userId); $user = User::find($userId);
if (!$user) { if (!$user) {
return false; return true;
} }
$user->t = time(); $user->t = time();
$user->u = $user->u + $u; $user->u = $user->u + $u;
@ -88,6 +88,8 @@ class UserService
if (!$user->save()) { if (!$user->save()) {
return false; return false;
} }
$mailService = new MailService();
$mailService->remindTraffic($user);
return true; return true;
} }
} }

View File

@ -11,7 +11,10 @@ class CacheKey
'SERVER_V2RAY_LAST_CHECK_AT' => '节点最后检查时间', 'SERVER_V2RAY_LAST_CHECK_AT' => '节点最后检查时间',
'SERVER_TROJAN_ONLINE_USER' => 'trojan节点在线用户', 'SERVER_TROJAN_ONLINE_USER' => 'trojan节点在线用户',
'SERVER_TROJAN_LAST_CHECK_AT' => 'trojan节点最后检查时间', 'SERVER_TROJAN_LAST_CHECK_AT' => 'trojan节点最后检查时间',
'TEMP_TOKEN' => '临时令牌' 'SERVER_SHADOWSOCKS_ONLINE_USER' => 'ss节点在线用户',
'SERVER_SHADOWSOCKS_LAST_CHECK_AT' => 'ss节点最后检查时间',
'TEMP_TOKEN' => '临时令牌',
'LAST_SEND_EMAIL_REMIND_TRAFFIC'
]; ];
public static function get(string $key, $uniqueValue) public static function get(string $key, $uniqueValue)

View File

@ -5,6 +5,19 @@ namespace App\Utils;
class Clash class Clash
{ {
public static function buildShadowsocks($uuid, $server)
{
$array = [];
$array['name'] = $server->name;
$array['type'] = 'ss';
$array['server'] = $server->host;
$array['port'] = $server->port;
$array['cipher'] = $server->cipher;
$array['password'] = $uuid;
$array['udp'] = true;
return $array;
}
public static function buildVmess($uuid, $server) public static function buildVmess($uuid, $server)
{ {
$array = []; $array = [];
@ -19,8 +32,8 @@ class Clash
if ($server->tls) { if ($server->tls) {
$tlsSettings = json_decode($server->tlsSettings); $tlsSettings = json_decode($server->tlsSettings);
$array['tls'] = true; $array['tls'] = true;
if (isset($tlsSettings->allowInsecure)) $array['skip-cert-verify'] = ($tlsSettings->allowInsecure ? true : false ); if (!empty($tlsSettings->allowInsecure)) $array['skip-cert-verify'] = ($tlsSettings->allowInsecure ? true : false );
if (isset($tlsSettings->serverName)) $array['servername'] = $tlsSettings->serverName; if (!empty($tlsSettings->serverName)) $array['servername'] = $tlsSettings->serverName;
} }
if ($server->network == 'ws') { if ($server->network == 'ws') {
$array['network'] = $server->network; $array['network'] = $server->network;
@ -44,12 +57,8 @@ class Clash
$array['port'] = $server->port; $array['port'] = $server->port;
$array['password'] = $password; $array['password'] = $password;
$array['udp'] = true; $array['udp'] = true;
$array['sni'] = $server->server_name; if (!empty($server->server_name)) $array['sni'] = $server->server_name;
if ($server->allow_insecure) { if (!empty($server->allow_insecure)) $array['skip-cert-verify'] = ($server->allow_insecure ? true : false );
$array['skip-cert-verify'] = true;
} else {
$array['skip-cert-verify'] = false;
}
return $array; return $array;
} }
} }

View File

@ -3,6 +3,7 @@
namespace App\Utils; namespace App\Utils;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan; use App\Models\ServerTrojan;
use App\Models\User; use App\Models\User;
@ -57,42 +58,6 @@ class Helper
return $str; return $str;
} }
public static function buildTrojanLink(ServerTrojan $server, User $user)
{
$server->name = rawurlencode($server->name);
$query = http_build_query([
'allowInsecure' => $server->allow_insecure,
'peer' => $server->server_name,
'sni' => $server->server_name
]);
$uri = "trojan://{$user->uuid}@{$server->host}:{$server->port}?{$query}#{$server->name}";
$uri .= "\r\n";
return $uri;
}
public static function buildVmessLink(Server $server, User $user)
{
$config = [
"v" => "2",
"ps" => $server->name,
"add" => $server->host,
"port" => $server->port,
"id" => $user->uuid,
"aid" => "2",
"net" => $server->network,
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server->tls ? "tls" : ""
];
if ((string)$server->network === 'ws') {
$wsSettings = json_decode($server->networkSettings);
if (isset($wsSettings->path)) $config['path'] = $wsSettings->path;
if (isset($wsSettings->headers->Host)) $config['host'] = $wsSettings->headers->Host;
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function multiPasswordVerify($algo, $password, $hash) public static function multiPasswordVerify($algo, $password, $hash)
{ {
switch($algo) { switch($algo) {

View File

@ -5,12 +5,30 @@ namespace App\Utils;
class QuantumultX class QuantumultX
{ {
public static function buildShadowsocks($password, $server)
{
$config = [
"shadowsocks={$server->host}:{$server->port}",
"method={$server->cipher}",
"password={$password}",
'fast-open=true',
'udp-relay=true',
"tag={$server->name}"
];
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildVmess($uuid, $server) public static function buildVmess($uuid, $server)
{ {
$config = [ $config = [
"vmess={$server->host}:{$server->port}", "vmess={$server->host}:{$server->port}",
"method=chacha20-poly1305", 'method=chacha20-poly1305',
"password={$uuid}", "password={$uuid}",
'fast-open=true',
'udp-relay=true',
"tag={$server->name}" "tag={$server->name}"
]; ];
if ($server->network === 'tcp') { if ($server->network === 'tcp') {
@ -21,7 +39,7 @@ class QuantumultX
// Tips: allowInsecure=false = tls-verification=true // Tips: allowInsecure=false = tls-verification=true
array_push($config, $tlsSettings->allowInsecure ? 'tls-verification=false' : 'tls-verification=true'); array_push($config, $tlsSettings->allowInsecure ? 'tls-verification=false' : 'tls-verification=true');
} }
if (isset($tlsSettings->serverName)) { if (!empty($tlsSettings->serverName)) {
array_push($config, "obfs-host={$tlsSettings->serverName}"); array_push($config, "obfs-host={$tlsSettings->serverName}");
} }
} }
@ -54,12 +72,12 @@ class QuantumultX
$config = [ $config = [
"trojan={$server->host}:{$server->port}", "trojan={$server->host}:{$server->port}",
"password={$password}", "password={$password}",
"over-tls=true", 'over-tls=true',
$server->server_name ? "tls-host={$server->server_name}" : "", $server->server_name ? "tls-host={$server->server_name}" : "",
// Tips: allowInsecure=false = tls-verification=true // Tips: allowInsecure=false = tls-verification=true
$server->allow_insecure ? 'tls-verification=false' : 'tls-verification=true', $server->allow_insecure ? 'tls-verification=false' : 'tls-verification=true',
"fast-open=false", 'fast-open=true',
"udp-relay=false", 'udp-relay=true',
"tag={$server->name}" "tag={$server->name}"
]; ];
$config = array_filter($config); $config = array_filter($config);

View File

@ -5,6 +5,17 @@ namespace App\Utils;
class Shadowrocket class Shadowrocket
{ {
public static function buildShadowsocks($password, $server)
{
$name = rawurlencode($server->name);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server->cipher}:{$password}")
);
return "ss://{$str}@{$server->host}:{$server->port}#{$name}\r\n";
}
public static function buildVmess($uuid, $server) public static function buildVmess($uuid, $server)
{ {
$userinfo = base64_encode('auto:' . $uuid . '@' . $server->host . ':' . $server->port); $userinfo = base64_encode('auto:' . $uuid . '@' . $server->host . ':' . $server->port);
@ -31,12 +42,12 @@ class Shadowrocket
public static function buildTrojan($password, $server) public static function buildTrojan($password, $server)
{ {
$server->name = rawurlencode($server->name); $name = rawurlencode($server->name);
$query = http_build_query([ $query = http_build_query([
'allowInsecure' => $server->allow_insecure, 'allowInsecure' => $server->allow_insecure,
'peer' => $server->server_name 'peer' => $server->server_name
]); ]);
$uri = "trojan://{$password}@{$server->host}:{$server->port}?{$query}&tfo=1#{$server->name}"; $uri = "trojan://{$password}@{$server->host}:{$server->port}?{$query}&tfo=1#{$name}";
$uri .= "\r\n"; $uri .= "\r\n";
return $uri; return $uri;
} }

66
app/Utils/Surfboard.php Normal file
View File

@ -0,0 +1,66 @@
<?php
namespace App\Utils;
class Surfboard
{
public static function buildShadowsocks($password, $server)
{
$config = [
"{$server->name}=custom",
"{$server->host}",
"{$server->port}",
"{$server->cipher}",
"{$password}",
'https://raw.githubusercontent.com/Hackl0us/proxy-tool-backup/master/SSEncrypt.module',
'tfo=true',
'udp-relay=true'
];
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildVmess($uuid, $server)
{
$config = [
"{$server->name}=vmess",
"{$server->host}",
"{$server->port}",
"username={$uuid}",
'tfo=true',
'udp-relay=true'
];
if ($server->network === 'tcp') {
if ($server->tls) {
$tlsSettings = json_decode($server->tlsSettings);
array_push($config, $server->tls ? 'tls=true' : 'tls=false');
if (!empty($tlsSettings->allowInsecure)) {
array_push($config, $tlsSettings->allowInsecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
}
}
}
if ($server->network === 'ws') {
array_push($config, 'ws=true');
if ($server->tls) {
$tlsSettings = json_decode($server->tlsSettings);
array_push($config, $server->tls ? 'tls=true' : 'tls=false');
if (!empty($tlsSettings->allowInsecure)) {
array_push($config, $tlsSettings->allowInsecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
}
}
if ($server->networkSettings) {
$wsSettings = json_decode($server->networkSettings);
if (isset($wsSettings->path)) array_push($config, "ws-path={$wsSettings->path}");
if (isset($wsSettings->headers->Host)) array_push($config, "ws-headers=host:{$wsSettings->headers->Host}");
}
}
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
}

View File

@ -5,26 +5,65 @@ namespace App\Utils;
class Surge class Surge
{ {
public static function buildShadowsocks($password, $server)
{
$config = [
"{$server->name}=ss",
"{$server->host}",
"{$server->port}",
"encrypt-method={$server->cipher}",
"password={$password}",
'tfo=true',
'udp-relay=true'
];
$config = array_filter($config);
$uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
}
public static function buildVmess($uuid, $server) public static function buildVmess($uuid, $server)
{ {
$proxies = $server->name . ' = vmess, ' . $server->host . ', ' . $server->port . ', username=' . $uuid . ', tfo=true'; $config = [
"{$server->name}=vmess",
"{$server->host}",
"{$server->port}",
"username={$uuid}",
'tfo=true',
'udp-relay=true'
];
if ($server->network === 'tcp') {
if ($server->tls) { if ($server->tls) {
$tlsSettings = json_decode($server->tlsSettings); $tlsSettings = json_decode($server->tlsSettings);
$proxies .= ', tls=' . ($server->tls ? "true" : "false"); array_push($config, $server->tls ? 'tls=true' : 'tls=false');
if (isset($tlsSettings->allowInsecure)) { if (!empty($tlsSettings->allowInsecure)) {
$proxies .= ', skip-cert-verify=' . ($tlsSettings->allowInsecure ? "true" : "false"); array_push($config, $tlsSettings->allowInsecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
}
if (!empty($tlsSettings->serverName)) {
array_push($config, "sni={$tlsSettings->serverName}");
}
}
}
if ($server->network === 'ws') {
array_push($config, 'ws=true');
if ($server->tls) {
$tlsSettings = json_decode($server->tlsSettings);
array_push($config, $server->tls ? 'tls=true' : 'tls=false');
if (!empty($tlsSettings->allowInsecure)) {
array_push($config, $tlsSettings->allowInsecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
} }
} }
if ($server->network == 'ws') {
$proxies .= ', ws=true';
if ($server->networkSettings) { if ($server->networkSettings) {
$wsSettings = json_decode($server->networkSettings); $wsSettings = json_decode($server->networkSettings);
if (isset($wsSettings->path)) $proxies .= ', ws-path=' . $wsSettings->path; if (isset($wsSettings->path)) array_push($config, "ws-path={$wsSettings->path}");
if (isset($wsSettings->headers->Host)) $proxies .= ', ws-headers=host:' . $wsSettings->headers->Host; if (isset($wsSettings->headers->Host)) array_push($config, "ws-headers=host:{$wsSettings->headers->Host}");
} }
} }
$proxies .= "\r\n";
return $proxies; $uri = implode(',', $config);
$uri .= "\r\n";
return $uri;
} }
public static function buildTrojan($password, $server) public static function buildTrojan($password, $server)
@ -34,10 +73,13 @@ class Surge
"{$server->host}", "{$server->host}",
"{$server->port}", "{$server->port}",
"password={$password}", "password={$password}",
$server->allow_insecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false',
$server->server_name ? "sni={$server->server_name}" : "", $server->server_name ? "sni={$server->server_name}" : "",
"tfo=true" 'tfo=true',
'udp-relay=true'
]; ];
if (!empty($server->allow_insecure)) {
array_push($config, $server->allow_insecure ? 'skip-cert-verify=true' : 'skip-cert-verify=false');
}
$config = array_filter($config); $config = array_filter($config);
$uri = implode(',', $config); $uri = implode(',', $config);
$uri .= "\r\n"; $uri .= "\r\n";

58
app/Utils/URLSchemes.php Normal file
View File

@ -0,0 +1,58 @@
<?php
namespace App\Utils;
use App\Models\Server;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Models\User;
class URLSchemes
{
public static function buildShadowsocks(ServerShadowsocks $server, User $user)
{
$name = rawurlencode($server->name);
$str = str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode("{$server->cipher}:{$user->uuid}")
);
return "ss://{$str}@{$server->host}:{$server->port}#{$name}\r\n";
}
public static function buildVmess(Server $server, User $user)
{
$config = [
"v" => "2",
"ps" => $server->name,
"add" => $server->host,
"port" => $server->port,
"id" => $user->uuid,
"aid" => "2",
"net" => $server->network,
"type" => "none",
"host" => "",
"path" => "",
"tls" => $server->tls ? "tls" : ""
];
if ((string)$server->network === 'ws') {
$wsSettings = json_decode($server->networkSettings);
if (isset($wsSettings->path)) $config['path'] = $wsSettings->path;
if (isset($wsSettings->headers->Host)) $config['host'] = $wsSettings->headers->Host;
}
return "vmess://" . base64_encode(json_encode($config)) . "\r\n";
}
public static function buildTrojan(ServerTrojan $server, User $user)
{
$name = rawurlencode($server->name);
$query = http_build_query([
'allowInsecure' => $server->allow_insecure,
'peer' => $server->server_name,
'sni' => $server->server_name
]);
$uri = "trojan://{$user->uuid}@{$server->host}:{$server->port}?{$query}#{$name}";
$uri .= "\r\n";
return $uri;
}
}

View File

@ -11,6 +11,7 @@
"require": { "require": {
"php": "^7.2", "php": "^7.2",
"fideloper/proxy": "^4.0", "fideloper/proxy": "^4.0",
"google/recaptcha": "^1.2",
"laravel/framework": "^6.0", "laravel/framework": "^6.0",
"laravel/tinker": "^1.0", "laravel/tinker": "^1.0",
"lokielse/omnipay-alipay": "3.0.6", "lokielse/omnipay-alipay": "3.0.6",

View File

@ -236,5 +236,5 @@ return [
| The only modification by laravel config | The only modification by laravel config
| |
*/ */
'version' => '1.3.2-d.1' 'version' => '1.4'
]; ];

View File

@ -22,7 +22,7 @@ CREATE TABLE `failed_jobs` (
DROP TABLE IF EXISTS `v2_coupon`; DROP TABLE IF EXISTS `v2_coupon`;
CREATE TABLE `v2_coupon` ( CREATE TABLE `v2_coupon` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`code` char(8) NOT NULL, `code` varchar(255) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL, `name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`type` tinyint(1) NOT NULL, `type` tinyint(1) NOT NULL,
`value` int(11) NOT NULL, `value` int(11) NOT NULL,
@ -49,6 +49,21 @@ CREATE TABLE `v2_invite_code` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_knowledge`;
CREATE TABLE `v2_knowledge` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`language` char(5) NOT NULL COMMENT '語言',
`category` varchar(255) NOT NULL COMMENT '分類名',
`title` varchar(255) NOT NULL COMMENT '標題',
`body` text NOT NULL COMMENT '內容',
`sort` int(11) DEFAULT NULL COMMENT '排序',
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT '顯示',
`created_at` int(11) NOT NULL COMMENT '創建時間',
`updated_at` int(11) NOT NULL COMMENT '更新時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='知識庫';
DROP TABLE IF EXISTS `v2_mail_log`; DROP TABLE IF EXISTS `v2_mail_log`;
CREATE TABLE `v2_mail_log` ( CREATE TABLE `v2_mail_log` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
@ -113,6 +128,8 @@ CREATE TABLE `v2_plan` (
`quarter_price` int(11) DEFAULT NULL, `quarter_price` int(11) DEFAULT NULL,
`half_year_price` int(11) DEFAULT NULL, `half_year_price` int(11) DEFAULT NULL,
`year_price` int(11) DEFAULT NULL, `year_price` int(11) DEFAULT NULL,
`two_year_price` int(11) DEFAULT NULL,
`three_year_price` int(11) DEFAULT NULL,
`onetime_price` int(11) DEFAULT NULL, `onetime_price` int(11) DEFAULT NULL,
`reset_price` int(11) DEFAULT NULL, `reset_price` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL, `created_at` int(11) NOT NULL,
@ -175,6 +192,26 @@ CREATE TABLE `v2_server_log` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server_shadowsocks`;
CREATE TABLE `v2_server_shadowsocks` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`tags` varchar(255) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`rate` varchar(11) NOT NULL,
`host` varchar(255) NOT NULL,
`port` int(11) NOT NULL,
`server_port` int(11) NOT NULL,
`cipher` varchar(255) NOT NULL,
`show` tinyint(4) NOT NULL DEFAULT '0',
`sort` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `v2_server_stat`; DROP TABLE IF EXISTS `v2_server_stat`;
CREATE TABLE `v2_server_stat` ( CREATE TABLE `v2_server_stat` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
@ -235,20 +272,6 @@ CREATE TABLE `v2_ticket_message` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_tutorial`;
CREATE TABLE `v2_tutorial` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`category_id` int(11) NOT NULL,
`title` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`steps` text,
`show` tinyint(1) NOT NULL DEFAULT '0',
`sort` int(11) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_user`; DROP TABLE IF EXISTS `v2_user`;
CREATE TABLE `v2_user` ( CREATE TABLE `v2_user` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
@ -265,9 +288,9 @@ CREATE TABLE `v2_user` (
`u` bigint(20) NOT NULL DEFAULT '0', `u` bigint(20) NOT NULL DEFAULT '0',
`d` bigint(20) NOT NULL DEFAULT '0', `d` bigint(20) NOT NULL DEFAULT '0',
`transfer_enable` bigint(20) NOT NULL DEFAULT '0', `transfer_enable` bigint(20) NOT NULL DEFAULT '0',
`enable` tinyint(1) NOT NULL DEFAULT '1',
`banned` tinyint(1) NOT NULL DEFAULT '0', `banned` tinyint(1) NOT NULL DEFAULT '0',
`is_admin` tinyint(1) NOT NULL DEFAULT '0', `is_admin` tinyint(1) NOT NULL DEFAULT '0',
`is_staff` tinyint(1) NOT NULL DEFAULT '0',
`last_login_at` int(11) DEFAULT NULL, `last_login_at` int(11) DEFAULT NULL,
`last_login_ip` int(11) DEFAULT NULL, `last_login_ip` int(11) DEFAULT NULL,
`uuid` varchar(36) NOT NULL, `uuid` varchar(36) NOT NULL,
@ -286,4 +309,4 @@ CREATE TABLE `v2_user` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8; ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2020-07-01 07:01:59 -- 2020-10-17 18:49:28

View File

@ -300,3 +300,45 @@ ADD `server_name` varchar(255) NULL AFTER `allow_insecure`;
UPDATE `v2_server` SET UPDATE `v2_server` SET
`ruleSettings` = NULL `ruleSettings` = NULL
WHERE `ruleSettings` = '{}'; WHERE `ruleSettings` = '{}';
ALTER TABLE `v2_plan`
ADD `two_year_price` int(11) NULL AFTER `year_price`,
ADD `three_year_price` int(11) NULL AFTER `two_year_price`;
ALTER TABLE `v2_user`
ADD `is_staff` tinyint(1) NOT NULL DEFAULT '0' AFTER `is_admin`;
CREATE TABLE `v2_server_shadowsocks` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`group_id` varchar(255) NOT NULL,
`parent_id` int(11) NULL,
`tags` varchar(255) NULL,
`name` varchar(255) NOT NULL,
`rate` varchar(11) NOT NULL,
`host` varchar(255) NOT NULL,
`port` int(11) NOT NULL,
`server_port` int(11) NOT NULL,
`cipher` varchar(255) NOT NULL,
`show` tinyint NOT NULL DEFAULT '0',
`sort` int(11) NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL
) COLLATE 'utf8mb4_general_ci';
ALTER TABLE `v2_coupon`
CHANGE `code` `code` varchar(255) COLLATE 'utf8_general_ci' NOT NULL AFTER `id`;
CREATE TABLE `v2_knowledge` (
`id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`language` char(5) NOT NULL COMMENT '語言',
`category` varchar(255) NOT NULL COMMENT '分類名',
`title` varchar(255) NOT NULL COMMENT '標題',
`body` text NOT NULL COMMENT '內容',
`sort` int(11) NULL COMMENT '排序',
`show` tinyint(1) NOT NULL DEFAULT '0' COMMENT '顯示',
`created_at` int(11) NOT NULL COMMENT '創建時間',
`updated_at` int(11) NOT NULL COMMENT '更新時間'
) COMMENT='知識庫' COLLATE 'utf8mb4_general_ci';
ALTER TABLE `v2_order`
ADD `coupon_id` int(11) NULL AFTER `plan_id`;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

24
public/assets/user/components.chunk.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,6 @@
- Laravel - Laravel
## Demo ## Demo
[Demo](https://v2board.com) [Demo](https://v2board.com)
## Document ## Document
@ -18,5 +17,8 @@
## Donation ## Donation
ETH&(USDT-ERC20): 0x84F85A89105B93F74c3b5db6410Ee8630F01063f ETH&(USDT-ERC20): 0x84F85A89105B93F74c3b5db6410Ee8630F01063f
## Sponsors
Thanks to the open source project license provided by [Jetbrains](https://www.jetbrains.com/)
## Other ## Other
Telegram Channel: [@v2board](https://t.me/v2board) Telegram Channel: [@v2board](https://t.me/v2board)

View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<link rel="stylesheet" href="/assets/admin/antd.chunk.css?v={{$verison}}"> <link rel="stylesheet" href="/assets/admin/components.chunk.css?v={{$verison}}">
<link rel="stylesheet" href="/assets/admin/umi.css?v={{$verison}}"> <link rel="stylesheet" href="/assets/admin/umi.css?v={{$verison}}">
<link rel="stylesheet" href="/assets/admin/custom.css?v={{$verison}}"> <link rel="stylesheet" href="/assets/admin/custom.css?v={{$verison}}">
<meta charset="utf-8"> <meta charset="utf-8">
@ -27,7 +27,7 @@
<body> <body>
<div id="root"></div> <div id="root"></div>
<script src="/assets/admin/vendors.async.js?v={{$verison}}"></script> <script src="/assets/admin/vendors.async.js?v={{$verison}}"></script>
<script src="/assets/admin/antd.async.js?v={{$verison}}"></script> <script src="/assets/admin/components.async.js?v={{$verison}}"></script>
<script src="/assets/admin/umi.js?v={{$verison}}"></script> <script src="/assets/admin/umi.js?v={{$verison}}"></script>
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-P1E9Z5LRRK"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-P1E9Z5LRRK"></script>

View File

@ -2,7 +2,7 @@
<html> <html>
<head> <head>
<link rel="stylesheet" href="/assets/user/antd.chunk.css?v={{$verison}}"> <link rel="stylesheet" href="/assets/user/components.chunk.css?v={{$verison}}">
<link rel="stylesheet" href="/assets/user/umi.css?v={{$verison}}"> <link rel="stylesheet" href="/assets/user/umi.css?v={{$verison}}">
<link rel="stylesheet" href="/assets/user/custom.css?v={{$verison}}"> <link rel="stylesheet" href="/assets/user/custom.css?v={{$verison}}">
<meta charset="utf-8"> <meta charset="utf-8">
@ -28,7 +28,7 @@
<body> <body>
<div id="root"></div> <div id="root"></div>
<script src="/assets/user/vendors.async.js?v={{$verison}}"></script> <script src="/assets/user/vendors.async.js?v={{$verison}}"></script>
<script src="/assets/user/antd.async.js?v={{$verison}}"></script> <script src="/assets/user/components.async.js?v={{$verison}}"></script>
<script src="/assets/user/umi.js?v={{$verison}}"></script> <script src="/assets/user/umi.js?v={{$verison}}"></script>
<!-- Global site tag (gtag.js) - Google Analytics --> <!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-P1E9Z5LRRK"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-P1E9Z5LRRK"></script>

View File

@ -1,4 +1,5 @@
git fetch --all && git reset --hard origin/master && git pull origin master git fetch --all && git reset --hard origin/master && git pull origin master
php composer.phar update -vvv
php artisan v2board:update php artisan v2board:update
php artisan config:cache php artisan config:cache
pm2 restart pm2.yaml pm2 restart pm2.yaml