Merge branch 'dev'
@ -98,6 +98,7 @@ class CheckCommission extends Command
|
||||
if (!$inviter) continue;
|
||||
if (!isset($commissionShareLevels[$l])) continue;
|
||||
$commissionBalance = $order->commission_balance * ($commissionShareLevels[$l] / 100);
|
||||
if (!$commissionBalance) continue;
|
||||
if ((int)config('v2board.withdraw_close_enable', 0)) {
|
||||
$inviter->balance = $inviter->balance + $commissionBalance;
|
||||
} else {
|
||||
|
51
app/Console/Commands/ClearUser.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Ticket;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearUser extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'clear:user';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '清理用户';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$builder = User::where('plan_id', NULL)
|
||||
->where('transfer_enable', 0)
|
||||
->where('expired_at', 0)
|
||||
->where('last_login_at', NULL);
|
||||
$count = $builder->count();
|
||||
if ($builder->delete()) {
|
||||
$this->info("已删除${count}位没有任何数据的用户");
|
||||
}
|
||||
}
|
||||
}
|
54
app/Console/Commands/ResetPassword.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ResetPassword extends Command
|
||||
{
|
||||
protected $builder;
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'reset:password {email}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '重置用户密码';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$user = User::where('email', $this->argument('email'))->first();
|
||||
if (!$user) abort(500, '邮箱不存在');
|
||||
$password = Helper::guid(false);
|
||||
$user->password = password_hash($password, PASSWORD_DEFAULT);
|
||||
$user->password_algo = null;
|
||||
if (!$user->save()) abort(500, '重置失败');
|
||||
$this->info("!!!重置成功!!!");
|
||||
$this->info("新密码为:{$password},请尽快修改密码。");
|
||||
}
|
||||
}
|
@ -69,6 +69,12 @@ class ResetTraffic extends Command
|
||||
// no action
|
||||
case 2:
|
||||
break;
|
||||
// year first day
|
||||
case 3:
|
||||
$this->resetByYearFirstDay($builder);
|
||||
// year expire day
|
||||
case 4:
|
||||
$this->resetByExpireYear($builder);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -85,7 +91,43 @@ class ResetTraffic extends Command
|
||||
case ($resetMethod['method'] === 2): {
|
||||
break;
|
||||
}
|
||||
case ($resetMethod['method'] === 3): {
|
||||
$builder = with(clone($this->builder))->whereIn('plan_id', $planIds);
|
||||
$this->resetByYearFirstDay($builder);
|
||||
break;
|
||||
}
|
||||
case ($resetMethod['method'] === 4): {
|
||||
$builder = with(clone($this->builder))->whereIn('plan_id', $planIds);
|
||||
$this->resetByExpireYear($builder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function resetByExpireYear($builder):void
|
||||
{
|
||||
$users = [];
|
||||
foreach ($builder->get() as $item) {
|
||||
$expireDay = date('m-d', $item->expired_at);
|
||||
$today = date('m-d');
|
||||
if ($expireDay === $today) {
|
||||
array_push($users, $item->id);
|
||||
}
|
||||
}
|
||||
User::whereIn('id', $users)->update([
|
||||
'u' => 0,
|
||||
'd' => 0
|
||||
]);
|
||||
}
|
||||
|
||||
private function resetByYearFirstDay($builder):void
|
||||
{
|
||||
if ((string)date('md') === '0101') {
|
||||
$builder->update([
|
||||
'u' => 0,
|
||||
'd' => 0
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ use App\Models\User;
|
||||
use App\Utils\CacheKey;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
use Illuminate\Foundation\Console\ConfigCacheCommand;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Matriphe\Larinfo;
|
||||
|
||||
|
@ -2,12 +2,10 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\StatServerJob;
|
||||
use Illuminate\Console\Command;
|
||||
use App\Models\Order;
|
||||
use App\Models\StatOrder;
|
||||
use App\Models\ServerLog;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\CommissionLog;
|
||||
|
||||
class V2boardStatistics extends Command
|
||||
{
|
||||
@ -50,14 +48,16 @@ class V2boardStatistics extends Command
|
||||
{
|
||||
$endAt = strtotime(date('Y-m-d'));
|
||||
$startAt = strtotime('-1 day', $endAt);
|
||||
$builder = Order::where('paid_at', '>=', $startAt)
|
||||
$orderBuilder = Order::where('paid_at', '>=', $startAt)
|
||||
->where('paid_at', '<', $endAt)
|
||||
->whereNotIn('status', [0, 2]);
|
||||
$orderCount = $builder->count();
|
||||
$orderAmount = $builder->sum('total_amount');
|
||||
$builder = $builder->whereNotNull('actual_commission_balance');
|
||||
$commissionCount = $builder->count();
|
||||
$commissionAmount = $builder->sum('actual_commission_balance');
|
||||
$orderCount = $orderBuilder->count();
|
||||
$orderAmount = $orderBuilder->sum('total_amount');
|
||||
$commissionBuilder = CommissionLog::where('created_at', '>=', $startAt)
|
||||
->where('created_at', '<', $endAt)
|
||||
->where('get_amount', '>', 0);
|
||||
$commissionCount = $commissionBuilder->count();
|
||||
$commissionAmount = $commissionBuilder->sum('get_amount');
|
||||
$data = [
|
||||
'order_count' => $orderCount,
|
||||
'order_amount' => $orderAmount,
|
||||
|
@ -3,7 +3,10 @@
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Support\Arr;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Throwable;
|
||||
use Facade\Ignition\Exceptions\ViewException;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
@ -50,6 +53,27 @@ class Handler extends ExceptionHandler
|
||||
*/
|
||||
public function render($request, Throwable $exception)
|
||||
{
|
||||
if ($exception instanceof ViewException) {
|
||||
return response([
|
||||
'message' => "主题初始化发生错误,请在后台对主题检查或配置后重试。"
|
||||
]);
|
||||
}
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
|
||||
|
||||
protected function convertExceptionToArray(Throwable $e)
|
||||
{
|
||||
return config('app.debug') ? [
|
||||
'message' => $e->getMessage(),
|
||||
'exception' => get_class($e),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'trace' => collect($e->getTrace())->map(function ($trace) {
|
||||
return Arr::except($trace, ['args']);
|
||||
})->all(),
|
||||
] : [
|
||||
'message' => $this->isHttpException($e) ? $e->getMessage() : __("Uh-oh, we've had some problems, we're working on it."),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,8 @@ use App\Services\TelegramService;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Utils\Dict;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class ConfigController extends Controller
|
||||
@ -54,23 +56,19 @@ class ConfigController extends Controller
|
||||
|
||||
public function setTelegramWebhook(Request $request)
|
||||
{
|
||||
$hookUrl = url('/api/v1/guest/telegram/webhook?access_token=' . md5(config('v2board.telegram_bot_token', $request->input('telegram_bot_token'))));
|
||||
$telegramService = new TelegramService($request->input('telegram_bot_token'));
|
||||
$telegramService->getMe();
|
||||
$telegramService->setWebhook(
|
||||
url(
|
||||
'/api/v1/guest/telegram/webhook?access_token=' . md5(config('v2board.telegram_bot_token', $request->input('telegram_bot_token')))
|
||||
)
|
||||
);
|
||||
$telegramService->setWebhook($hookUrl);
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function fetch()
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
// TODO: default should be in Dict
|
||||
return response([
|
||||
'data' => [
|
||||
$key = $request->input('key');
|
||||
$data = [
|
||||
'invite' => [
|
||||
'invite_force' => (int)config('v2board.invite_force', 0),
|
||||
'invite_commission' => config('v2board.invite_commission', 10),
|
||||
@ -87,6 +85,8 @@ class ConfigController extends Controller
|
||||
'commission_distribution_l3' => config('v2board.commission_distribution_l3')
|
||||
],
|
||||
'site' => [
|
||||
'logo' => config('v2board.logo'),
|
||||
'force_https' => (int)config('v2board.force_https', 0),
|
||||
'safe_mode_enable' => (int)config('v2board.safe_mode_enable', 0),
|
||||
'stop_register' => (int)config('v2board.stop_register', 0),
|
||||
'email_verify' => (int)config('v2board.email_verify', 0),
|
||||
@ -104,7 +104,10 @@ class ConfigController extends Controller
|
||||
'recaptcha_site_key' => config('v2board.recaptcha_site_key'),
|
||||
'tos_url' => config('v2board.tos_url'),
|
||||
'currency' => config('v2board.currency', 'CNY'),
|
||||
'currency_symbol' => config('v2board.currency_symbol', '¥')
|
||||
'currency_symbol' => config('v2board.currency_symbol', '¥'),
|
||||
'register_limit_by_ip_enable' => (int)config('v2board.register_limit_by_ip_enable', 0),
|
||||
'register_limit_count' => config('v2board.register_limit_count', 3),
|
||||
'register_limit_expire' => config('v2board.register_limit_expire', 60)
|
||||
],
|
||||
'subscribe' => [
|
||||
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
|
||||
@ -113,37 +116,7 @@ class ConfigController extends Controller
|
||||
'new_order_event_id' => (int)config('v2board.new_order_event_id', 0),
|
||||
'renew_order_event_id' => (int)config('v2board.renew_order_event_id', 0),
|
||||
'change_order_event_id' => (int)config('v2board.change_order_event_id', 0),
|
||||
],
|
||||
'pay' => [
|
||||
// alipay
|
||||
'alipay_enable' => (int)config('v2board.alipay_enable'),
|
||||
'alipay_appid' => config('v2board.alipay_appid'),
|
||||
'alipay_pubkey' => config('v2board.alipay_pubkey'),
|
||||
'alipay_privkey' => config('v2board.alipay_privkey'),
|
||||
// stripe
|
||||
'stripe_alipay_enable' => (int)config('v2board.stripe_alipay_enable', 0),
|
||||
'stripe_wepay_enable' => (int)config('v2board.stripe_wepay_enable', 0),
|
||||
'stripe_card_enable' => (int)config('v2board.stripe_card_enable', 0),
|
||||
'stripe_sk_live' => config('v2board.stripe_sk_live'),
|
||||
'stripe_pk_live' => config('v2board.stripe_pk_live'),
|
||||
'stripe_webhook_key' => config('v2board.stripe_webhook_key'),
|
||||
'stripe_currency' => config('v2board.stripe_currency', 'hkd'),
|
||||
// bitpayx
|
||||
'bitpayx_name' => config('v2board.bitpayx_name', '在线支付'),
|
||||
'bitpayx_enable' => (int)config('v2board.bitpayx_enable', 0),
|
||||
'bitpayx_appsecret' => config('v2board.bitpayx_appsecret'),
|
||||
// mGate
|
||||
'mgate_name' => config('v2board.mgate_name', '在线支付'),
|
||||
'mgate_enable' => (int)config('v2board.mgate_enable', 0),
|
||||
'mgate_url' => config('v2board.mgate_url'),
|
||||
'mgate_app_id' => config('v2board.mgate_app_id'),
|
||||
'mgate_app_secret' => config('v2board.mgate_app_secret'),
|
||||
// Epay
|
||||
'epay_name' => config('v2board.epay_name', '在线支付'),
|
||||
'epay_enable' => (int)config('v2board.epay_enable', 0),
|
||||
'epay_url' => config('v2board.epay_url'),
|
||||
'epay_pid' => config('v2board.epay_pid'),
|
||||
'epay_key' => config('v2board.epay_key'),
|
||||
'show_info_to_server_enable' => (int)config('v2board.show_info_to_server_enable', 0)
|
||||
],
|
||||
'frontend' => [
|
||||
'frontend_theme' => config('v2board.frontend_theme', 'v2board'),
|
||||
@ -151,9 +124,7 @@ class ConfigController extends Controller
|
||||
'frontend_theme_header' => config('v2board.frontend_theme_header', 'dark'),
|
||||
'frontend_theme_color' => config('v2board.frontend_theme_color', 'default'),
|
||||
'frontend_background_url' => config('v2board.frontend_background_url'),
|
||||
'frontend_admin_path' => config('v2board.frontend_admin_path', 'admin'),
|
||||
'frontend_customer_service_method' => config('v2board.frontend_customer_service_method', 0),
|
||||
'frontend_customer_service_id' => config('v2board.frontend_customer_service_id'),
|
||||
'frontend_admin_path' => config('v2board.frontend_admin_path', 'admin')
|
||||
],
|
||||
'server' => [
|
||||
'server_token' => config('v2board.server_token'),
|
||||
@ -184,22 +155,35 @@ class ConfigController extends Controller
|
||||
'android_version' => config('v2board.android_version'),
|
||||
'android_download_url' => config('v2board.android_download_url')
|
||||
]
|
||||
];
|
||||
if ($key && isset($data[$key])) {
|
||||
return response([
|
||||
'data' => [
|
||||
$key => $data[$key]
|
||||
]
|
||||
]);
|
||||
};
|
||||
// TODO: default should be in Dict
|
||||
return response([
|
||||
'data' => $data
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(ConfigSave $request)
|
||||
{
|
||||
$data = $request->validated();
|
||||
$array = \Config::get('v2board');
|
||||
foreach ($data as $k => $v) {
|
||||
if (!in_array($k, array_keys($request->validated()))) {
|
||||
abort(500, '参数' . $k . '不在规则内,禁止修改');
|
||||
$config = config('v2board');
|
||||
foreach (ConfigSave::RULES as $k => $v) {
|
||||
if (!in_array($k, array_keys(ConfigSave::RULES))) {
|
||||
unset($config[$k]);
|
||||
continue;
|
||||
}
|
||||
$array[$k] = $v;
|
||||
if (array_key_exists($k, $data)) {
|
||||
$config[$k] = $data[$k];
|
||||
}
|
||||
$data = var_export($array, 1);
|
||||
if (!\File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) {
|
||||
}
|
||||
$data = var_export($config, 1);
|
||||
if (!File::put(base_path() . '/config/v2board.php', "<?php\n return $data ;")) {
|
||||
abort(500, '修改失败');
|
||||
}
|
||||
if (function_exists('opcache_reset')) {
|
||||
@ -207,7 +191,7 @@ class ConfigController extends Controller
|
||||
abort(500, '缓存清除失败,请卸载或检查opcache配置状态');
|
||||
}
|
||||
}
|
||||
\Artisan::call('config:cache');
|
||||
Artisan::call('config:cache');
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
|
@ -88,7 +88,16 @@ class CouponController extends Controller
|
||||
array_push($coupons, $coupon);
|
||||
}
|
||||
DB::beginTransaction();
|
||||
if (!Coupon::insert($coupons)) {
|
||||
if (!Coupon::insert(array_map(function ($item) use ($coupon) {
|
||||
// format data
|
||||
if (isset($item['limit_plan_ids']) && is_array($item['limit_plan_ids'])) {
|
||||
$item['limit_plan_ids'] = json_encode($coupon['limit_plan_ids']);
|
||||
}
|
||||
if (isset($item['limit_period']) && is_array($item['limit_period'])) {
|
||||
$item['limit_period'] = json_encode($coupon['limit_period']);
|
||||
}
|
||||
return $item;
|
||||
}, $coupons))) {
|
||||
DB::rollBack();
|
||||
abort(500, '生成失败');
|
||||
}
|
||||
|
@ -22,7 +22,8 @@ class NoticeController extends Controller
|
||||
$data = $request->only([
|
||||
'title',
|
||||
'content',
|
||||
'img_url'
|
||||
'img_url',
|
||||
'tags'
|
||||
]);
|
||||
if (!$request->input('id')) {
|
||||
if (!Notice::create($data)) {
|
||||
|
@ -46,35 +46,50 @@ class PaymentController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Request $request)
|
||||
{
|
||||
$payment = Payment::find($request->input('id'));
|
||||
if (!$payment) abort(500, '支付方式不存在');
|
||||
$payment->enable = !$payment->enable;
|
||||
if (!$payment->save()) abort(500, '保存失败');
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(Request $request)
|
||||
{
|
||||
if (!config('v2board.app_url')) {
|
||||
abort(500, '请在站点配置中配置站点地址');
|
||||
}
|
||||
if ($request->input('id')) {
|
||||
$payment = Payment::find($request->input('id'));
|
||||
if (!$payment) abort(500, '支付方式不存在');
|
||||
try {
|
||||
$payment->update($request->input());
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '更新失败');
|
||||
}
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
$params = $request->validate([
|
||||
'name' => 'required',
|
||||
'icon' => 'nullable',
|
||||
'payment' => 'required',
|
||||
'config' => 'required',
|
||||
'notify_domain' => 'nullable|url'
|
||||
'notify_domain' => 'nullable|url',
|
||||
'handling_fee_fixed' => 'nullable|integer',
|
||||
'handling_fee_percent' => 'nullable|numeric|between:0.1,100'
|
||||
], [
|
||||
'name.required' => '显示名称不能为空',
|
||||
'payment.required' => '网关参数不能为空',
|
||||
'config.required' => '配置参数不能为空',
|
||||
'notify_domain.url' => '自定义通知域名格式有误'
|
||||
'notify_domain.url' => '自定义通知域名格式有误',
|
||||
'handling_fee_fixed.integer' => '固定手续费格式有误',
|
||||
'handling_fee_percent.between' => '百分比手续费范围须在0.1-100之间'
|
||||
]);
|
||||
if ($request->input('id')) {
|
||||
$payment = Payment::find($request->input('id'));
|
||||
if (!$payment) abort(500, '支付方式不存在');
|
||||
try {
|
||||
$payment->update($params);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, $e->getMessage());
|
||||
}
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
$params['uuid'] = Helper::randomChar(8);
|
||||
if (!Payment::create($params)) {
|
||||
abort(500, '保存失败');
|
||||
|
@ -16,7 +16,6 @@ class PlanController extends Controller
|
||||
{
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
|
||||
$counts = User::select(
|
||||
DB::raw("plan_id"),
|
||||
DB::raw("count(*) as count")
|
||||
|
@ -71,12 +71,12 @@ class StatController extends Controller
|
||||
'value' => $statistic['order_count']
|
||||
]);
|
||||
array_push($result, [
|
||||
'type' => '佣金金额',
|
||||
'type' => '佣金金额(已发放)',
|
||||
'date' => $date,
|
||||
'value' => $statistic['commission_amount'] / 100
|
||||
]);
|
||||
array_push($result, [
|
||||
'type' => '佣金笔数',
|
||||
'type' => '佣金笔数(已发放)',
|
||||
'date' => $date,
|
||||
'value' => $statistic['commission_count']
|
||||
]);
|
||||
@ -94,7 +94,8 @@ class StatController extends Controller
|
||||
'vmess' => ServerV2ray::where('parent_id', null)->get()->toArray(),
|
||||
'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray()
|
||||
];
|
||||
$timestamp = strtotime('-1 day', strtotime(date('Y-m-d')));
|
||||
$startAt = strtotime('-1 day', strtotime(date('Y-m-d')));
|
||||
$endAt = strtotime(date('Y-m-d'));
|
||||
$statistics = StatServer::select([
|
||||
'server_id',
|
||||
'server_type',
|
||||
@ -102,7 +103,8 @@ class StatController extends Controller
|
||||
'd',
|
||||
DB::raw('(u+d) as total')
|
||||
])
|
||||
->where('record_at', '>=', $timestamp)
|
||||
->where('record_at', '>=', $startAt)
|
||||
->where('record_at', '<', $endAt)
|
||||
->where('record_type', 'd')
|
||||
->limit(10)
|
||||
->orderBy('total', 'DESC')
|
||||
|
90
app/Http/Controllers/Admin/ThemeController.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\ThemeService;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ThemeController extends Controller
|
||||
{
|
||||
private $themes;
|
||||
private $path;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->path = $path = public_path('theme/');
|
||||
$this->themes = array_map(function ($item) use ($path) {
|
||||
return str_replace($path, '', $item);
|
||||
}, glob($path . '*'));
|
||||
}
|
||||
|
||||
public function getThemes()
|
||||
{
|
||||
$themeConfigs = [];
|
||||
foreach ($this->themes as $theme) {
|
||||
$themeConfigFile = $this->path . "{$theme}/config.php";
|
||||
if (!File::exists($themeConfigFile)) continue;
|
||||
$themeConfig = include($themeConfigFile);
|
||||
if (!isset($themeConfig['configs']) || !is_array($themeConfig)) continue;
|
||||
$themeConfigs[$theme] = $themeConfig;
|
||||
if (config("theme.{$theme}")) continue;
|
||||
$themeService = new ThemeService($theme);
|
||||
$themeService->init();
|
||||
}
|
||||
return response([
|
||||
'data' => [
|
||||
'themes' => $themeConfigs,
|
||||
'active' => config('v2board.frontend_theme', 'v2board')
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function getThemeConfig(Request $request)
|
||||
{
|
||||
$payload = $request->validate([
|
||||
'name' => 'required|in:' . join(',', $this->themes)
|
||||
]);
|
||||
return response([
|
||||
'data' => config("theme.{$payload['name']}")
|
||||
]);
|
||||
}
|
||||
|
||||
public function saveThemeConfig(Request $request)
|
||||
{
|
||||
$payload = $request->validate([
|
||||
'name' => 'required|in:' . join(',', $this->themes),
|
||||
'config' => 'required'
|
||||
]);
|
||||
$payload['config'] = json_decode(base64_decode($payload['config']), true);
|
||||
if (!$payload['config'] || !is_array($payload['config'])) abort(500, '参数有误');
|
||||
$themeConfigFile = public_path("theme/{$payload['name']}/config.php");
|
||||
if (!File::exists($themeConfigFile)) abort(500, '主题不存在');
|
||||
$themeConfig = include($themeConfigFile);
|
||||
$validateFields = array_column($themeConfig['configs'], 'field_name');
|
||||
$config = [];
|
||||
foreach ($validateFields as $validateField) {
|
||||
$config[$validateField] = isset($payload['config'][$validateField]) ? $payload['config'][$validateField] : '';
|
||||
}
|
||||
|
||||
File::ensureDirectoryExists(base_path() . '/config/theme/');
|
||||
|
||||
$data = var_export($config, 1);
|
||||
if (!File::put(base_path() . "/config/theme/{$payload['name']}.php", "<?php\n return $data ;")) {
|
||||
abort(500, '修改失败');
|
||||
}
|
||||
|
||||
try {
|
||||
Artisan::call('config:cache');
|
||||
// sleep(2);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '保存失败');
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => $config
|
||||
]);
|
||||
}
|
||||
}
|
@ -36,20 +36,20 @@ class TicketController extends Controller
|
||||
}
|
||||
$current = $request->input('current') ? $request->input('current') : 1;
|
||||
$pageSize = $request->input('pageSize') >= 10 ? $request->input('pageSize') : 10;
|
||||
$model = Ticket::orderBy('created_at', 'DESC');
|
||||
$model = Ticket::orderBy('updated_at', 'DESC');
|
||||
if ($request->input('status') !== NULL) {
|
||||
$model->where('status', $request->input('status'));
|
||||
}
|
||||
if ($request->input('reply_status') !== NULL) {
|
||||
$model->whereIn('reply_status', $request->input('reply_status'));
|
||||
}
|
||||
if ($request->input('email') !== NULL) {
|
||||
$user = User::where('email', $request->input('email'))->first();
|
||||
if ($user) $model->where('user_id', $user->id);
|
||||
}
|
||||
$total = $model->count();
|
||||
$res = $model->forPage($current, $pageSize)
|
||||
->get();
|
||||
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
|
||||
|
@ -74,7 +74,7 @@ class UserController extends Controller
|
||||
$res[$i]['plan_name'] = $plan[$k]['name'];
|
||||
}
|
||||
}
|
||||
$res[$i]['subscribe_url'] = Helper::getSubscribeHost() . '/api/v1/client/subscribe?token=' . $res[$i]['token'];
|
||||
$res[$i]['subscribe_url'] = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $res[$i]['token']);
|
||||
}
|
||||
return response([
|
||||
'data' => $res,
|
||||
@ -153,7 +153,6 @@ class UserController extends Controller
|
||||
}
|
||||
|
||||
$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;
|
||||
@ -161,7 +160,7 @@ class UserController extends Controller
|
||||
$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'];
|
||||
$subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']);
|
||||
$data .= "{$user['email']},{$balance},{$commissionBalance},{$transferEnable},{$notUseFlow},{$expireDate},{$planName},{$subscribeUrl}\r\n";
|
||||
}
|
||||
echo "\xEF\xBB\xBF" . $data;
|
||||
@ -232,12 +231,11 @@ class UserController extends Controller
|
||||
}
|
||||
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'];
|
||||
$subscribeUrl = Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user['token']);
|
||||
$data .= "{$user['email']},{$password},{$expireDate},{$user['uuid']},{$createDate},{$subscribeUrl}\r\n";
|
||||
}
|
||||
echo $data;
|
||||
|
@ -23,6 +23,7 @@ class ClientController extends Controller
|
||||
if ($userService->isAvailable($user)) {
|
||||
$serverService = new ServerService();
|
||||
$servers = $serverService->getAvailableServers($user);
|
||||
$this->setSubscribeInfoToServers($servers, $user);
|
||||
if ($flag) {
|
||||
foreach (glob(app_path('Http//Controllers//Client//Protocols') . '/*.php') as $file) {
|
||||
$file = 'App\\Http\\Controllers\\Client\\Protocols\\' . basename($file, '.php');
|
||||
@ -38,4 +39,26 @@ class ClientController extends Controller
|
||||
die('该客户端暂不支持进行订阅');
|
||||
}
|
||||
}
|
||||
|
||||
private function setSubscribeInfoToServers(&$servers, $user)
|
||||
{
|
||||
if (!(int)config('v2board.show_info_to_server_enable', 0)) return;
|
||||
$useTraffic = round($user['u'] / (1024*1024*1024), 2) + round($user['d'] / (1024*1024*1024), 2);
|
||||
$totalTraffic = round($user['transfer_enable'] / (1024*1024*1024), 2);
|
||||
$remainingTraffic = $totalTraffic - $useTraffic;
|
||||
$expiredDate = $user['expired_at'] ? date('Y-m-d', $user['expired_at']) : '长期有效';
|
||||
$userService = new UserService();
|
||||
$resetDay = $userService->getResetDay($user);
|
||||
array_unshift($servers, array_merge($servers[0], [
|
||||
'name' => "套餐到期:{$expiredDate}",
|
||||
]));
|
||||
if ($resetDay) {
|
||||
array_unshift($servers, array_merge($servers[0], [
|
||||
'name' => "距离下次重置剩余:{$resetDay} 天",
|
||||
]));
|
||||
}
|
||||
array_unshift($servers, array_merge($servers[0], [
|
||||
'name' => "剩余流量:{$remainingTraffic} GB",
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class Clash
|
||||
$appName = config('v2board.app_name', 'V2Board');
|
||||
header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
|
||||
header('profile-update-interval: 24');
|
||||
header("content-disposition:attachment;filename={$appName}");
|
||||
header("content-disposition:attachment;filename*=UTF-8''".rawurlencode($appName));
|
||||
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
|
||||
$customConfig = base_path() . '/resources/rules/custom.clash.yaml';
|
||||
if (\File::exists($customConfig)) {
|
||||
@ -133,7 +133,7 @@ class Clash
|
||||
if ($server['networkSettings']) {
|
||||
$grpcSettings = $server['networkSettings'];
|
||||
$array['grpc-opts'] = [];
|
||||
$array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName'];
|
||||
if (isset($grpcSettings['serviceName'])) $array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Http\Controllers\Client\Protocols;
|
||||
|
||||
class AnXray
|
||||
class SagerNet
|
||||
{
|
||||
public $flag = 'axxray';
|
||||
public $flag = 'sagernet';
|
||||
private $servers;
|
||||
private $user;
|
||||
|
||||
@ -74,7 +74,7 @@ class AnXray
|
||||
}
|
||||
if ((string)$server['network'] === 'ws') {
|
||||
$wsSettings = $server['networkSettings'];
|
||||
if (isset($wsSettings['path'])) $config['path'] = urlencode($wsSettings['path']);
|
||||
if (isset($wsSettings['path'])) $config['path'] = $wsSettings['path'];
|
||||
if (isset($wsSettings['headers']['Host'])) $config['host'] = urlencode($wsSettings['headers']['Host']);
|
||||
}
|
||||
if ((string)$server['network'] === 'grpc') {
|
@ -23,7 +23,7 @@ class Stash
|
||||
$appName = config('v2board.app_name', 'V2Board');
|
||||
header("subscription-userinfo: upload={$user['u']}; download={$user['d']}; total={$user['transfer_enable']}; expire={$user['expired_at']}");
|
||||
header('profile-update-interval: 24');
|
||||
header("content-disposition: filename={$appName}");
|
||||
header("content-disposition: filename*=UTF-8''".rawurlencode($appName));
|
||||
// 暂时使用clash配置文件,后续根据Stash更新情况更新
|
||||
$defaultConfig = base_path() . '/resources/rules/default.clash.yaml';
|
||||
$customConfig = base_path() . '/resources/rules/custom.clash.yaml';
|
||||
@ -134,7 +134,7 @@ class Stash
|
||||
if ($server['networkSettings']) {
|
||||
$grpcSettings = $server['networkSettings'];
|
||||
$array['grpc-opts'] = [];
|
||||
$array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName'];
|
||||
if (isset($grpcSettings['serviceName'])) $array['grpc-opts']['grpc-service-name'] = $grpcSettings['serviceName'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Client\Protocols;
|
||||
|
||||
use App\Utils\Helper;
|
||||
|
||||
class Surfboard
|
||||
{
|
||||
@ -53,7 +54,7 @@ class Surfboard
|
||||
}
|
||||
|
||||
// Subscription link
|
||||
$subsURL = config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user['token'];
|
||||
$subsURL = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}");
|
||||
$subsDomain = $_SERVER['SERVER_NAME'];
|
||||
|
||||
$config = str_replace('$subs_link', $subsURL, $config);
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Http\Controllers\Client\Protocols;
|
||||
|
||||
use App\Utils\Helper;
|
||||
|
||||
class Surge
|
||||
{
|
||||
public $flag = 'surge';
|
||||
@ -52,6 +54,7 @@ class Surge
|
||||
}
|
||||
|
||||
// Subscription link
|
||||
$subsURL = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}");
|
||||
$subsDomain = $_SERVER['SERVER_NAME'];
|
||||
$subsURL = 'https://' . $subsDomain . '/api/v1/client/subscribe?token=' . $user['token'];
|
||||
|
||||
|
@ -21,7 +21,8 @@ class CommController extends Controller
|
||||
'is_recaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
|
||||
'recaptcha_site_key' => config('v2board.recaptcha_site_key'),
|
||||
'app_description' => config('v2board.app_description'),
|
||||
'app_url' => config('v2board.app_url')
|
||||
'app_url' => config('v2board.app_url'),
|
||||
'logo' => config('v2board.logo'),
|
||||
]
|
||||
]);
|
||||
}
|
||||
@ -34,11 +35,4 @@ class CommController extends Controller
|
||||
}
|
||||
return $suffix;
|
||||
}
|
||||
|
||||
public function getHitokoto()
|
||||
{
|
||||
return response([
|
||||
'data' => Http::get('https://v1.hitokoto.cn/')->json()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ class PaymentController extends Controller
|
||||
if (!$order) {
|
||||
abort(500, 'order is not found');
|
||||
}
|
||||
if ($order->status === 1) return true;
|
||||
if ($order->status !== 0) return true;
|
||||
$orderService = new OrderService($order);
|
||||
if (!$orderService->paid($callbackNo)) {
|
||||
return false;
|
||||
|
@ -20,6 +20,12 @@ class AuthController extends Controller
|
||||
{
|
||||
public function register(AuthRegister $request)
|
||||
{
|
||||
if ((int)config('v2board.register_limit_by_ip_enable', 0)) {
|
||||
$registerCountByIP = Cache::get(CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip())) ?? 0;
|
||||
if ((int)$registerCountByIP >= (int)config('v2board.register_limit_count', 3)) {
|
||||
abort(500, __('Register frequently, please try again after 1 hour'));
|
||||
}
|
||||
}
|
||||
if ((int)config('v2board.recaptcha_enable', 0)) {
|
||||
$recaptcha = new ReCaptcha(config('v2board.recaptcha_key'));
|
||||
$recaptchaResp = $recaptcha->verify($request->input('recaptcha_data'));
|
||||
@ -109,6 +115,16 @@ class AuthController extends Controller
|
||||
];
|
||||
$request->session()->put('email', $user->email);
|
||||
$request->session()->put('id', $user->id);
|
||||
$user->last_login_at = time();
|
||||
$user->save();
|
||||
|
||||
if ((int)config('v2board.register_limit_by_ip_enable', 0)) {
|
||||
Cache::put(
|
||||
CacheKey::get('REGISTER_IP_RATE_LIMIT', $request->ip()),
|
||||
(int)$registerCountByIP + 1,
|
||||
(int)config('v2board.register_limit_expire', 60) * 60
|
||||
);
|
||||
}
|
||||
return response()->json([
|
||||
'data' => $data
|
||||
]);
|
||||
|
@ -20,6 +20,7 @@ use Illuminate\Support\Facades\Cache;
|
||||
*/
|
||||
class DeepbworkController extends Controller
|
||||
{
|
||||
CONST V2RAY_CONFIG = '{"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"api":{"services":["HandlerService","StatsService"],"tag":"api"},"dns":{},"stats":{},"inbounds":[{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled":true,"destOverride":["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},{"listen":"127.0.0.1","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"outbounds":[{"protocol":"freedom","settings":{}},{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"type":"field","inboundTag":"api","outboundTag":"api"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$token = $request->input('token');
|
||||
@ -52,13 +53,16 @@ class DeepbworkController extends Controller
|
||||
"level" => 0,
|
||||
];
|
||||
unset($user['uuid']);
|
||||
unset($user['email']);
|
||||
array_push($result, $user);
|
||||
}
|
||||
$eTag = sha1(json_encode($result));
|
||||
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
|
||||
abort(304);
|
||||
}
|
||||
return response([
|
||||
'msg' => 'ok',
|
||||
'data' => $result,
|
||||
]);
|
||||
])->header('ETag', "\"{$eTag}\"");
|
||||
}
|
||||
|
||||
// 后端提交数据
|
||||
@ -97,13 +101,133 @@ class DeepbworkController extends Controller
|
||||
if (empty($nodeId) || empty($localPort)) {
|
||||
abort(500, '参数错误');
|
||||
}
|
||||
$serverService = new ServerService();
|
||||
try {
|
||||
$json = $serverService->getV2RayConfig($nodeId, $localPort);
|
||||
$json = $this->getV2RayConfig($nodeId, $localPort);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, $e->getMessage());
|
||||
}
|
||||
|
||||
die(json_encode($json, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
private function getV2RayConfig(int $nodeId, int $localPort)
|
||||
{
|
||||
$server = ServerV2ray::find($nodeId);
|
||||
if (!$server) {
|
||||
abort(500, '节点不存在');
|
||||
}
|
||||
$json = json_decode(self::V2RAY_CONFIG);
|
||||
$json->log->loglevel = (int)config('v2board.server_log_enable') ? 'debug' : 'none';
|
||||
$json->inbounds[1]->port = (int)$localPort;
|
||||
$json->inbounds[0]->port = (int)$server->server_port;
|
||||
$json->inbounds[0]->streamSettings->network = $server->network;
|
||||
$this->setDns($server, $json);
|
||||
$this->setNetwork($server, $json);
|
||||
$this->setRule($server, $json);
|
||||
$this->setTls($server, $json);
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
private function setDns(ServerV2ray $server, object $json)
|
||||
{
|
||||
if ($server->dnsSettings) {
|
||||
$dns = $server->dnsSettings;
|
||||
if (isset($dns->servers)) {
|
||||
array_push($dns->servers, '1.1.1.1');
|
||||
array_push($dns->servers, 'localhost');
|
||||
}
|
||||
$json->dns = $dns;
|
||||
$json->outbounds[0]->settings->domainStrategy = 'UseIP';
|
||||
}
|
||||
}
|
||||
|
||||
private function setNetwork(ServerV2ray $server, object $json)
|
||||
{
|
||||
if ($server->networkSettings) {
|
||||
switch ($server->network) {
|
||||
case 'tcp':
|
||||
$json->inbounds[0]->streamSettings->tcpSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'kcp':
|
||||
$json->inbounds[0]->streamSettings->kcpSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'ws':
|
||||
$json->inbounds[0]->streamSettings->wsSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'http':
|
||||
$json->inbounds[0]->streamSettings->httpSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'domainsocket':
|
||||
$json->inbounds[0]->streamSettings->dsSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'quic':
|
||||
$json->inbounds[0]->streamSettings->quicSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'grpc':
|
||||
$json->inbounds[0]->streamSettings->grpcSettings = $server->networkSettings;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function setRule(ServerV2ray $server, object $json)
|
||||
{
|
||||
$domainRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_domain')));
|
||||
$protocolRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_protocol')));
|
||||
if ($server->ruleSettings) {
|
||||
$ruleSettings = $server->ruleSettings;
|
||||
// domain
|
||||
if (isset($ruleSettings->domain)) {
|
||||
$ruleSettings->domain = array_filter($ruleSettings->domain);
|
||||
if (!empty($ruleSettings->domain)) {
|
||||
$domainRules = array_merge($domainRules, $ruleSettings->domain);
|
||||
}
|
||||
}
|
||||
// protocol
|
||||
if (isset($ruleSettings->protocol)) {
|
||||
$ruleSettings->protocol = array_filter($ruleSettings->protocol);
|
||||
if (!empty($ruleSettings->protocol)) {
|
||||
$protocolRules = array_merge($protocolRules, $ruleSettings->protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($domainRules)) {
|
||||
$domainObj = new \StdClass();
|
||||
$domainObj->type = 'field';
|
||||
$domainObj->domain = $domainRules;
|
||||
$domainObj->outboundTag = 'block';
|
||||
array_push($json->routing->rules, $domainObj);
|
||||
}
|
||||
if (!empty($protocolRules)) {
|
||||
$protocolObj = new \StdClass();
|
||||
$protocolObj->type = 'field';
|
||||
$protocolObj->protocol = $protocolRules;
|
||||
$protocolObj->outboundTag = 'block';
|
||||
array_push($json->routing->rules, $protocolObj);
|
||||
}
|
||||
if (empty($domainRules) && empty($protocolRules)) {
|
||||
$json->inbounds[0]->sniffing->enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private function setTls(ServerV2ray $server, object $json)
|
||||
{
|
||||
if ((int)$server->tls) {
|
||||
$tlsSettings = $server->tlsSettings;
|
||||
$json->inbounds[0]->streamSettings->security = 'tls';
|
||||
$tls = (object)[
|
||||
'certificateFile' => '/root/.cert/server.crt',
|
||||
'keyFile' => '/root/.cert/server.key'
|
||||
];
|
||||
$json->inbounds[0]->streamSettings->tlsSettings = new \StdClass();
|
||||
if (isset($tlsSettings->serverName)) {
|
||||
$json->inbounds[0]->streamSettings->tlsSettings->serverName = (string)$tlsSettings->serverName;
|
||||
}
|
||||
if (isset($tlsSettings->allowInsecure)) {
|
||||
$json->inbounds[0]->streamSettings->tlsSettings->allowInsecure = (int)$tlsSettings->allowInsecure ? true : false;
|
||||
}
|
||||
$json->inbounds[0]->streamSettings->tlsSettings->certificates[0] = $tls;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,9 +48,13 @@ class ShadowsocksTidalabController extends Controller
|
||||
'secret' => $user->uuid
|
||||
]);
|
||||
}
|
||||
$eTag = sha1(json_encode($result));
|
||||
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
|
||||
abort(304);
|
||||
}
|
||||
return response([
|
||||
'data' => $result
|
||||
]);
|
||||
])->header('ETag', "\"{$eTag}\"");
|
||||
}
|
||||
|
||||
// 后端提交数据
|
||||
|
@ -20,6 +20,7 @@ use Illuminate\Support\Facades\Cache;
|
||||
*/
|
||||
class TrojanTidalabController extends Controller
|
||||
{
|
||||
CONST TROJAN_CONFIG = '{"run_type":"server","local_addr":"0.0.0.0","local_port":443,"remote_addr":"www.taobao.com","remote_port":80,"password":[],"ssl":{"cert":"server.crt","key":"server.key","sni":"domain.com"},"api":{"enabled":true,"api_addr":"127.0.0.1","api_port":10000}}';
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$token = $request->input('token');
|
||||
@ -49,13 +50,16 @@ class TrojanTidalabController extends Controller
|
||||
"password" => $user->uuid,
|
||||
];
|
||||
unset($user['uuid']);
|
||||
unset($user['email']);
|
||||
array_push($result, $user);
|
||||
}
|
||||
$eTag = sha1(json_encode($result));
|
||||
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
|
||||
abort(304);
|
||||
}
|
||||
return response([
|
||||
'msg' => 'ok',
|
||||
'data' => $result,
|
||||
]);
|
||||
])->header('ETag', "\"{$eTag}\"");
|
||||
}
|
||||
|
||||
// 后端提交数据
|
||||
@ -94,13 +98,28 @@ class TrojanTidalabController extends Controller
|
||||
if (empty($nodeId) || empty($localPort)) {
|
||||
abort(500, '参数错误');
|
||||
}
|
||||
$serverService = new ServerService();
|
||||
try {
|
||||
$json = $serverService->getTrojanConfig($nodeId, $localPort);
|
||||
$json = $this->getTrojanConfig($nodeId, $localPort);
|
||||
} catch (\Exception $e) {
|
||||
abort(500, $e->getMessage());
|
||||
}
|
||||
|
||||
die(json_encode($json, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
private function getTrojanConfig(int $nodeId, int $localPort)
|
||||
{
|
||||
$server = ServerTrojan::find($nodeId);
|
||||
if (!$server) {
|
||||
abort(500, '节点不存在');
|
||||
}
|
||||
|
||||
$json = json_decode(self::TROJAN_CONFIG);
|
||||
$json->local_port = $server->server_port;
|
||||
$json->ssl->sni = $server->server_name ? $server->server_name : $server->host;
|
||||
$json->ssl->cert = "/root/.cert/server.crt";
|
||||
$json->ssl->key = "/root/.cert/server.key";
|
||||
$json->api->api_port = $localPort;
|
||||
return $json;
|
||||
}
|
||||
}
|
||||
|
128
app/Http/Controllers/Server/VProxyController.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Server;
|
||||
|
||||
use App\Services\ServerService;
|
||||
use App\Services\UserService;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ServerShadowsocks;
|
||||
use App\Models\ServerV2ray;
|
||||
use App\Models\ServerTrojan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class VProxyController extends Controller
|
||||
{
|
||||
private $nodeType;
|
||||
private $nodeInfo;
|
||||
private $nodeId;
|
||||
private $token;
|
||||
|
||||
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');
|
||||
}
|
||||
$this->token = $token;
|
||||
$this->nodeType = $request->input('node_type');
|
||||
$this->nodeId = $request->input('node_id');
|
||||
switch ($this->nodeType) {
|
||||
case 'v2ray':
|
||||
$this->nodeInfo = ServerV2ray::find($this->nodeId);
|
||||
break;
|
||||
case 'shadowsocks':
|
||||
$this->nodeInfo = ServerShadowsocks::find($this->nodeId);
|
||||
break;
|
||||
case 'trojan':
|
||||
$this->nodeInfo = ServerTrojan::find($this->nodeId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!$this->nodeInfo) {
|
||||
abort(500, 'server not found');
|
||||
}
|
||||
}
|
||||
|
||||
// 后端获取用户
|
||||
public function user(Request $request)
|
||||
{
|
||||
ini_set('memory_limit', -1);
|
||||
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_CHECK_AT', $this->nodeInfo->id), time(), 3600);
|
||||
$serverService = new ServerService();
|
||||
$users = $serverService->getAvailableUsers($this->nodeInfo->group_id);
|
||||
$users = $users->toArray();
|
||||
|
||||
$response['users'] = $users;
|
||||
|
||||
switch ($this->nodeType) {
|
||||
case 'shadowsocks':
|
||||
$response['server'] = [
|
||||
'cipher' => $this->nodeInfo->cipher,
|
||||
'server_port' => $this->nodeInfo->server_port
|
||||
];
|
||||
break;
|
||||
}
|
||||
|
||||
$eTag = sha1(json_encode($response));
|
||||
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
|
||||
abort(304);
|
||||
}
|
||||
|
||||
return response($response)->header('ETag', "\"{$eTag}\"");
|
||||
}
|
||||
|
||||
// 后端提交数据
|
||||
public function submit(Request $request)
|
||||
{
|
||||
$data = file_get_contents('php://input');
|
||||
$data = json_decode($data, true);
|
||||
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_ONLINE_USER', $this->nodeInfo->id), count($data), 3600);
|
||||
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_PUSH_AT', $this->nodeInfo->id), time(), 3600);
|
||||
$userService = new UserService();
|
||||
foreach ($data as $item) {
|
||||
$u = $item['u'] * $this->nodeInfo->rate;
|
||||
$d = $item['d'] * $this->nodeInfo->rate;
|
||||
$userService->trafficFetch($u, $d, $item['user_id'], $this->nodeInfo, $this->nodeType);
|
||||
}
|
||||
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
// 后端获取配置
|
||||
public function config(Request $request)
|
||||
{
|
||||
switch ($this->nodeType) {
|
||||
case 'shadowsocks':
|
||||
die(json_encode([
|
||||
'server_port' => $this->nodeInfo->server_port,
|
||||
'cipher' => $this->nodeInfo->cipher,
|
||||
'obfs' => $this->nodeInfo->obfs,
|
||||
'obfs_settings' => $this->nodeInfo->obfs_settings
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
break;
|
||||
case 'v2ray':
|
||||
die(json_encode([
|
||||
'server_port' => $this->nodeInfo->server_port,
|
||||
'network' => $this->nodeInfo->network,
|
||||
'cipher' => $this->nodeInfo->cipher,
|
||||
'networkSettings' => $this->nodeInfo->networkSettings,
|
||||
'tls' => $this->nodeInfo->tls
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
break;
|
||||
case 'trojan':
|
||||
die(json_encode([
|
||||
'host' => $this->nodeInfo->host,
|
||||
'server_port' => $this->nodeInfo->server_port
|
||||
], JSON_UNESCAPED_UNICODE));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -39,13 +39,6 @@ class TicketController extends Controller
|
||||
$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
|
||||
|
@ -29,6 +29,7 @@ class InviteController extends Controller
|
||||
{
|
||||
return response([
|
||||
'data' => CommissionLog::where('invite_user_id', $request->session()->get('id'))
|
||||
->where('get_amount', '>', 0)
|
||||
->select([
|
||||
'id',
|
||||
'trade_no',
|
||||
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers\User;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Services\UserService;
|
||||
use App\Utils\Helper;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Knowledge;
|
||||
|
||||
@ -28,12 +29,7 @@ class KnowledgeController extends Controller
|
||||
$appleIdPassword = __('No active subscription. Unable to use our provided Apple ID');
|
||||
$this->formatAccessData($knowledge['body']);
|
||||
}
|
||||
$subscribeUrl = config('v2board.app_url', env('APP_URL'));
|
||||
$subscribeUrls = explode(',', config('v2board.subscribe_url'));
|
||||
if ($subscribeUrls) {
|
||||
$subscribeUrl = $subscribeUrls[rand(0, count($subscribeUrls) - 1)];
|
||||
}
|
||||
$subscribeUrl = "{$subscribeUrl}/api/v1/client/subscribe?token={$user['token']}";
|
||||
$subscribeUrl = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}");
|
||||
$knowledge['body'] = str_replace('{{siteName}}', config('v2board.app_name', 'V2Board'), $knowledge['body']);
|
||||
$knowledge['body'] = str_replace('{{subscribeUrl}}', $subscribeUrl, $knowledge['body']);
|
||||
$knowledge['body'] = str_replace('{{urlEncodeSubscribeUrl}}', urlencode($subscribeUrl), $knowledge['body']);
|
||||
@ -63,10 +59,12 @@ class KnowledgeController extends Controller
|
||||
|
||||
private function formatAccessData(&$body)
|
||||
{
|
||||
function getBetween($input, $start, $end){$substr = substr($input, strlen($start)+strpos($input, $start),(strlen($input) - strpos($input, $end))*(-1));return $substr;}
|
||||
function getBetween($input, $start, $end){$substr = substr($input, strlen($start)+strpos($input, $start),(strlen($input) - strpos($input, $end))*(-1));return $start . $substr . $end;}
|
||||
while (strpos($body, '<!--access start-->') !== false) {
|
||||
$accessData = getBetween($body, '<!--access start-->', '<!--access end-->');
|
||||
if ($accessData) {
|
||||
$body = str_replace($accessData, '<div class="v2board-no-access">'. __('You must have a valid subscription to view content in this area') .'</div>', $body);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,8 +87,12 @@ class OrderController extends Controller
|
||||
}
|
||||
|
||||
if ($request->input('period') === 'reset_price') {
|
||||
if ($user->expired_at <= time() || !$user->plan_id) {
|
||||
if (!$user->plan_id) {
|
||||
abort(500, __('Subscription has expired or no active subscription, unable to purchase Data Reset Package'));
|
||||
} else {
|
||||
if ($user->plan_id !== $request->input('plan_id')) {
|
||||
abort(500, __('This subscription reset package does not apply to your subscription'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,13 +188,17 @@ class OrderController extends Controller
|
||||
$payment = Payment::find($method);
|
||||
if (!$payment || $payment->enable !== 1) abort(500, __('Payment method is not available'));
|
||||
$paymentService = new PaymentService($payment->payment, $payment->id);
|
||||
if ($payment->handling_fee_fixed || $payment->handling_fee_percent) {
|
||||
$order->handling_amount = round(($order->total_amount * ($payment->handling_fee_percent / 100)) + $payment->handling_fee_fixed);
|
||||
}
|
||||
$order->payment_id = $method;
|
||||
if (!$order->save()) abort(500, __('Request failed, please try again later'));
|
||||
$result = $paymentService->pay([
|
||||
'trade_no' => $tradeNo,
|
||||
'total_amount' => $order->total_amount,
|
||||
'total_amount' => isset($order->handling_amount) ? ($order->total_amount + $order->handling_amount) : $order->total_amount,
|
||||
'user_id' => $order->user_id,
|
||||
'stripe_token' => $request->input('token')
|
||||
]);
|
||||
$order->update(['payment_id' => $method]);
|
||||
return response([
|
||||
'type' => $result['type'],
|
||||
'data' => $result['data']
|
||||
@ -217,7 +225,9 @@ class OrderController extends Controller
|
||||
'id',
|
||||
'name',
|
||||
'payment',
|
||||
'icon'
|
||||
'icon',
|
||||
'handling_fee_fixed',
|
||||
'handling_fee_percent'
|
||||
])
|
||||
->where('enable', 1)->get();
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers\User;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Plan;
|
||||
|
||||
@ -10,12 +11,15 @@ class PlanController extends Controller
|
||||
{
|
||||
public function fetch(Request $request)
|
||||
{
|
||||
$user = User::find($request->session()->get('id'));
|
||||
if ($request->input('id')) {
|
||||
$plan = Plan::where('id', $request->input('id'))
|
||||
->first();
|
||||
$plan = Plan::where('id', $request->input('id'))->first();
|
||||
if (!$plan) {
|
||||
abort(500, __('Subscription plan does not exist'));
|
||||
}
|
||||
if ((!$plan->show && !$plan->renew) || (!$plan->show && $user->plan_id !== $plan->id)) {
|
||||
abort(500, __('Subscription plan does not exist'));
|
||||
}
|
||||
return response([
|
||||
'data' => $plan
|
||||
]);
|
||||
|
@ -12,15 +12,14 @@ class StatController extends Controller
|
||||
public function getTrafficLog(Request $request)
|
||||
{
|
||||
$builder = StatUser::select([
|
||||
DB::raw('sum(u) as u'),
|
||||
DB::raw('sum(d) as d'),
|
||||
'u',
|
||||
'd',
|
||||
'record_at',
|
||||
'user_id',
|
||||
'server_rate'
|
||||
])
|
||||
->where('user_id', $request->session()->get('id'))
|
||||
->where('record_at', '>=', strtotime(date('Y-m-1')))
|
||||
->groupBy('record_at', 'user_id', 'server_rate')
|
||||
->orderBy('record_at', 'DESC');
|
||||
return response([
|
||||
'data' => $builder->get()
|
||||
|
@ -8,6 +8,7 @@ use App\Http\Requests\User\TicketWithdraw;
|
||||
use App\Jobs\SendTelegramJob;
|
||||
use App\Models\User;
|
||||
use App\Services\TelegramService;
|
||||
use App\Services\TicketService;
|
||||
use App\Utils\Dict;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\Ticket;
|
||||
@ -40,13 +41,6 @@ class TicketController extends Controller
|
||||
$ticket = Ticket::where('user_id', $request->session()->get('id'))
|
||||
->orderBy('created_at', 'DESC')
|
||||
->get();
|
||||
for ($i = 0; $i < count($ticket); $i++) {
|
||||
if ($ticket[$i]['last_reply_user_id'] == $request->session()->get('id')) {
|
||||
$ticket[$i]['reply_status'] = 0;
|
||||
} else {
|
||||
$ticket[$i]['reply_status'] = 1;
|
||||
}
|
||||
}
|
||||
return response([
|
||||
'data' => $ticket
|
||||
]);
|
||||
@ -55,15 +49,14 @@ class TicketController extends Controller
|
||||
public function save(TicketSave $request)
|
||||
{
|
||||
DB::beginTransaction();
|
||||
if ((int)Ticket::where('status', 0)->where('user_id', $request->session()->get('id'))->count()) {
|
||||
if ((int)Ticket::where('status', 0)->where('user_id', $request->session()->get('id'))->lockForUpdate()->count()) {
|
||||
abort(500, __('There are other unresolved tickets'));
|
||||
}
|
||||
$ticket = Ticket::create(array_merge($request->only([
|
||||
'subject',
|
||||
'level'
|
||||
]), [
|
||||
'user_id' => $request->session()->get('id'),
|
||||
'last_reply_user_id' => $request->session()->get('id')
|
||||
'user_id' => $request->session()->get('id')
|
||||
]));
|
||||
if (!$ticket) {
|
||||
DB::rollback();
|
||||
@ -79,7 +72,7 @@ class TicketController extends Controller
|
||||
abort(500, __('Failed to open ticket'));
|
||||
}
|
||||
DB::commit();
|
||||
$this->sendNotify($ticket, $ticketMessage);
|
||||
$this->sendNotify($ticket, $request->input('message'));
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
@ -105,19 +98,15 @@ class TicketController extends Controller
|
||||
if ($request->session()->get('id') == $this->getLastMessage($ticket->id)->user_id) {
|
||||
abort(500, __('Please wait for the technical enginneer to reply'));
|
||||
}
|
||||
DB::beginTransaction();
|
||||
$ticketMessage = TicketMessage::create([
|
||||
'user_id' => $request->session()->get('id'),
|
||||
'ticket_id' => $ticket->id,
|
||||
'message' => $request->input('message')
|
||||
]);
|
||||
$ticket->last_reply_user_id = $request->session()->get('id');
|
||||
if (!$ticketMessage || !$ticket->save()) {
|
||||
DB::rollback();
|
||||
$ticketService = new TicketService();
|
||||
if (!$ticketService->reply(
|
||||
$ticket,
|
||||
$request->input('message'),
|
||||
$request->session()->get('id')
|
||||
)) {
|
||||
abort(500, __('Ticket reply failed'));
|
||||
}
|
||||
DB::commit();
|
||||
$this->sendNotify($ticket, $ticketMessage);
|
||||
$this->sendNotify($ticket, $request->input('message'));
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
@ -175,8 +164,7 @@ class TicketController extends Controller
|
||||
$ticket = Ticket::create([
|
||||
'subject' => $subject,
|
||||
'level' => 2,
|
||||
'user_id' => $request->session()->get('id'),
|
||||
'last_reply_user_id' => $request->session()->get('id')
|
||||
'user_id' => $request->session()->get('id')
|
||||
]);
|
||||
if (!$ticket) {
|
||||
DB::rollback();
|
||||
@ -196,15 +184,15 @@ class TicketController extends Controller
|
||||
abort(500, __('Failed to open ticket'));
|
||||
}
|
||||
DB::commit();
|
||||
$this->sendNotify($ticket, $ticketMessage);
|
||||
$this->sendNotify($ticket, $message);
|
||||
return response([
|
||||
'data' => true
|
||||
]);
|
||||
}
|
||||
|
||||
private function sendNotify(Ticket $ticket, TicketMessage $ticketMessage)
|
||||
private function sendNotify(Ticket $ticket, string $message)
|
||||
{
|
||||
$telegramService = new TelegramService();
|
||||
$telegramService->sendMessageWithAdmin("📮工单提醒 #{$ticket->id}\n———————————————\n主题:\n`{$ticket->subject}`\n内容:\n`{$ticketMessage->message}`", true);
|
||||
$telegramService->sendMessageWithAdmin("📮工单提醒 #{$ticket->id}\n———————————————\n主题:\n`{$ticket->subject}`\n内容:\n`{$message}`", true);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\User\UserTransfer;
|
||||
use App\Http\Requests\User\UserUpdate;
|
||||
use App\Http\Requests\User\UserChangePassword;
|
||||
use App\Services\UserService;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
@ -120,8 +121,9 @@ class UserController extends Controller
|
||||
abort(500, __('Subscription plan does not exist'));
|
||||
}
|
||||
}
|
||||
$user['subscribe_url'] = Helper::getSubscribeHost() . "/api/v1/client/subscribe?token={$user['token']}";
|
||||
$user['reset_day'] = $this->getResetDay($user);
|
||||
$user['subscribe_url'] = Helper::getSubscribeUrl("/api/v1/client/subscribe?token={$user['token']}");
|
||||
$userService = new UserService();
|
||||
$user['reset_day'] = $userService->getResetDay($user);
|
||||
return response([
|
||||
'data' => $user
|
||||
]);
|
||||
@ -139,7 +141,7 @@ class UserController extends Controller
|
||||
abort(500, __('Reset failed'));
|
||||
}
|
||||
return response([
|
||||
'data' => config('v2board.subscribe_url', config('v2board.app_url', env('APP_URL'))) . '/api/v1/client/subscribe?token=' . $user->token
|
||||
'data' => Helper::getSubscribeUrl('/api/v1/client/subscribe?token=' . $user->token)
|
||||
]);
|
||||
}
|
||||
|
||||
@ -184,36 +186,6 @@ class UserController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
private function getResetDay(User $user)
|
||||
{
|
||||
if ($user->expired_at <= time() || $user->expired_at === NULL) return null;
|
||||
// if reset method is not reset
|
||||
if (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 2) return null;
|
||||
$day = date('d', $user->expired_at);
|
||||
$today = date('d');
|
||||
$lastDay = date('d', strtotime('last day of +0 months'));
|
||||
|
||||
if ((int)config('v2board.reset_traffic_method') === 0 ||
|
||||
(isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 0))
|
||||
{
|
||||
return $lastDay - $today;
|
||||
}
|
||||
if ((int)config('v2board.reset_traffic_method') === 1 ||
|
||||
(isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 1))
|
||||
{
|
||||
if ((int)$day >= (int)$today && (int)$day >= (int)$lastDay) {
|
||||
return $lastDay - $today;
|
||||
}
|
||||
if ((int)$day >= (int)$today) {
|
||||
return $day - $today;
|
||||
} else {
|
||||
return $lastDay - $today + $day;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function getQuickLoginUrl(Request $request)
|
||||
{
|
||||
$user = User::find($request->session()->get('id'));
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Utils\CacheKey;
|
||||
use Closure;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class Client
|
||||
{
|
||||
|
@ -6,16 +6,8 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ConfigSave extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
const RULES = [
|
||||
// invite & commission
|
||||
'safe_mode_enable' => 'in:0,1',
|
||||
'invite_force' => 'in:0,1',
|
||||
'invite_commission' => 'integer',
|
||||
'invite_gen_limit' => 'integer',
|
||||
@ -30,6 +22,9 @@ class ConfigSave extends FormRequest
|
||||
'commission_distribution_l2' => 'nullable|numeric',
|
||||
'commission_distribution_l3' => 'nullable|numeric',
|
||||
// site
|
||||
'logo' => 'nullable|url',
|
||||
'force_https' => 'in:0,1',
|
||||
'safe_mode_enable' => 'in:0,1',
|
||||
'stop_register' => 'in:0,1',
|
||||
'email_verify' => 'in:0,1',
|
||||
'app_name' => '',
|
||||
@ -48,48 +43,23 @@ class ConfigSave extends FormRequest
|
||||
'tos_url' => 'nullable|url',
|
||||
'currency' => '',
|
||||
'currency_symbol' => '',
|
||||
'register_limit_by_ip_enable' => 'in:0,1',
|
||||
'register_limit_count' => 'integer',
|
||||
'register_limit_expire' => 'integer',
|
||||
// subscribe
|
||||
'plan_change_enable' => 'in:0,1',
|
||||
'reset_traffic_method' => 'in:0,1,2',
|
||||
'reset_traffic_method' => 'in:0,1,2,3,4',
|
||||
'surplus_enable' => 'in:0,1',
|
||||
'new_order_event_id' => 'in:0,1',
|
||||
'renew_order_event_id' => 'in:0,1',
|
||||
'change_order_event_id' => 'in:0,1',
|
||||
'show_info_to_server_enable' => 'in:0,1',
|
||||
// server
|
||||
'server_token' => 'nullable|min:16',
|
||||
'server_license' => 'nullable',
|
||||
'server_log_enable' => 'in:0,1',
|
||||
'server_v2ray_domain' => '',
|
||||
'server_v2ray_protocol' => '',
|
||||
// alipay
|
||||
'alipay_enable' => 'in:0,1',
|
||||
'alipay_appid' => 'nullable|integer|min:16',
|
||||
'alipay_pubkey' => 'max:2048',
|
||||
'alipay_privkey' => 'max:2048',
|
||||
// stripe
|
||||
'stripe_alipay_enable' => 'in:0,1',
|
||||
'stripe_wepay_enable' => 'in:0,1',
|
||||
'stripe_card_enable' => 'in:0,1',
|
||||
'stripe_sk_live' => '',
|
||||
'stripe_pk_live' => '',
|
||||
'stripe_webhook_key' => '',
|
||||
'stripe_currency' => 'in:hkd,usd,sgd,eur,gbp,jpy,cad',
|
||||
// bitpayx
|
||||
'bitpayx_name' => '',
|
||||
'bitpayx_enable' => 'in:0,1',
|
||||
'bitpayx_appsecret' => '',
|
||||
// mGate
|
||||
'mgate_name' => '',
|
||||
'mgate_enable' => 'in:0,1',
|
||||
'mgate_url' => 'nullable|url',
|
||||
'mgate_app_id' => '',
|
||||
'mgate_app_secret' => '',
|
||||
// Epay
|
||||
'epay_name' => '',
|
||||
'epay_enable' => 'in:0,1',
|
||||
'epay_url' => 'nullable|url',
|
||||
'epay_pid' => '',
|
||||
'epay_key' => '',
|
||||
// frontend
|
||||
'frontend_theme' => '',
|
||||
'frontend_theme_sidebar' => 'in:dark,light',
|
||||
@ -97,8 +67,6 @@ class ConfigSave extends FormRequest
|
||||
'frontend_theme_color' => 'in:default,darkblue,black,green',
|
||||
'frontend_background_url' => 'nullable|url',
|
||||
'frontend_admin_path' => '',
|
||||
'frontend_customer_service_method' => '',
|
||||
'frontend_customer_service_id' => '',
|
||||
// email
|
||||
'email_template' => '',
|
||||
'email_host' => '',
|
||||
@ -121,6 +89,14 @@ class ConfigSave extends FormRequest
|
||||
'android_version' => '',
|
||||
'android_download_url' => ''
|
||||
];
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return self::RULES;
|
||||
}
|
||||
|
||||
public function messages()
|
||||
@ -131,7 +107,8 @@ class ConfigSave extends FormRequest
|
||||
'subscribe_url.url' => '订阅URL格式不正确,必须携带http(s)://',
|
||||
'server_token.min' => '通讯密钥长度必须大于16位',
|
||||
'tos_url.url' => '服务条款URL格式不正确,必须携带http(s)://',
|
||||
'telegram_discuss_link.url' => 'Telegram群组地址必须为URL格式,必须携带http(s)://'
|
||||
'telegram_discuss_link.url' => 'Telegram群组地址必须为URL格式,必须携带http(s)://',
|
||||
'logo.url' => 'LOGO URL格式不正确,必须携带https(s)://'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,8 @@ class NoticeSave extends FormRequest
|
||||
return [
|
||||
'title' => 'required',
|
||||
'content' => 'required',
|
||||
'img_url' => 'nullable|url'
|
||||
'img_url' => 'nullable|url',
|
||||
'tags' => 'nullable|array'
|
||||
];
|
||||
}
|
||||
|
||||
@ -25,7 +26,8 @@ class NoticeSave extends FormRequest
|
||||
return [
|
||||
'title.required' => '标题不能为空',
|
||||
'content.required' => '内容不能为空',
|
||||
'img_url.url' => '图片URL格式不正确'
|
||||
'img_url.url' => '图片URL格式不正确',
|
||||
'tags.array' => '标签格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class PlanSave extends FormRequest
|
||||
'three_year_price' => 'nullable|integer',
|
||||
'onetime_price' => 'nullable|integer',
|
||||
'reset_price' => 'nullable|integer',
|
||||
'reset_traffic_method' => 'nullable|integer|in:0,1,2'
|
||||
'reset_traffic_method' => 'nullable|integer|in:0,1,2,3,4'
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,8 @@ class ServerShadowsocksSave extends FormRequest
|
||||
'port' => 'required',
|
||||
'server_port' => 'required',
|
||||
'cipher' => 'required|in:aes-128-gcm,aes-256-gcm,chacha20-ietf-poly1305',
|
||||
'obfs' => 'nullable|in:http',
|
||||
'obfs_settings' => 'nullable|array',
|
||||
'tags' => 'nullable|array',
|
||||
'rate' => 'required|numeric'
|
||||
];
|
||||
@ -40,7 +42,9 @@ class ServerShadowsocksSave extends FormRequest
|
||||
'cipher.required' => '加密方式不能为空',
|
||||
'tags.array' => '标签格式不正确',
|
||||
'rate.required' => '倍率不能为空',
|
||||
'rate.numeric' => '倍率格式不正确'
|
||||
'rate.numeric' => '倍率格式不正确',
|
||||
'obfs.in' => '混淆格式不正确',
|
||||
'obfs_settings.array' => '混淆设置格式不正确'
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -111,8 +111,13 @@ class AdminRoute
|
||||
$router->post('/payment/getPaymentForm', 'Admin\\PaymentController@getPaymentForm');
|
||||
$router->post('/payment/save', 'Admin\\PaymentController@save');
|
||||
$router->post('/payment/drop', 'Admin\\PaymentController@drop');
|
||||
$router->post('/payment/show', 'Admin\\PaymentController@show');
|
||||
// System
|
||||
$router->get ('/system/getStatus', 'Admin\\SystemController@getStatus');
|
||||
// Theme
|
||||
$router->get ('/theme/getThemes', 'Admin\\ThemeController@getThemes');
|
||||
$router->post('/theme/saveThemeConfig', 'Admin\\ThemeController@saveThemeConfig');
|
||||
$router->post('/theme/getThemeConfig', 'Admin\\ThemeController@getThemeConfig');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ class GuestRoute
|
||||
$router->match(['get', 'post'], '/payment/notify/{method}/{uuid}', 'Guest\\PaymentController@notify');
|
||||
// Comm
|
||||
$router->get ('/comm/config', 'Guest\\CommController@config');
|
||||
$router->get ('/comm/getHitokoto', 'Guest\\CommController@getHitokoto');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ class StatServerJob implements ShouldQueue
|
||||
|
||||
$data = StatServer::where('record_at', $recordAt)
|
||||
->where('server_id', $this->server->id)
|
||||
->where('server_type', $this->protocol)
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
if ($data) {
|
||||
|
@ -52,7 +52,7 @@ class StatUserJob implements ShouldQueue
|
||||
}
|
||||
|
||||
$data = StatUser::where('record_at', $recordAt)
|
||||
->where('server_id', $this->server->id)
|
||||
->where('server_rate', $this->server->rate)
|
||||
->where('user_id', $this->userId)
|
||||
->first();
|
||||
if ($data) {
|
||||
@ -67,8 +67,6 @@ class StatUserJob implements ShouldQueue
|
||||
} else {
|
||||
if (!StatUser::create([
|
||||
'user_id' => $this->userId,
|
||||
'server_id' => $this->server->id,
|
||||
'server_type' => $this->protocol,
|
||||
'server_rate' => $this->server->rate,
|
||||
'u' => $this->u,
|
||||
'd' => $this->d,
|
||||
|
@ -11,6 +11,7 @@ class Notice extends Model
|
||||
protected $guarded = ['id'];
|
||||
protected $casts = [
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp'
|
||||
'updated_at' => 'timestamp',
|
||||
'tags' => 'array'
|
||||
];
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ class ServerShadowsocks extends Model
|
||||
'created_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp',
|
||||
'group_id' => 'array',
|
||||
'tags' => 'array'
|
||||
'tags' => 'array',
|
||||
'obfs_settings' => 'array'
|
||||
];
|
||||
}
|
||||
|
@ -28,7 +28,8 @@ class CoinPayments {
|
||||
];
|
||||
}
|
||||
|
||||
public function pay($order) {
|
||||
public function pay($order)
|
||||
{
|
||||
|
||||
// IPN notifications are slow, when the transaction is successful, we should return to the user center to avoid user confusion
|
||||
$parseUrl = parse_url($order['return_url']);
|
||||
@ -53,12 +54,12 @@ class CoinPayments {
|
||||
|
||||
return [
|
||||
'type' => 1, // Redirect to url
|
||||
'data' => 'https://www.coinpayments.net/index.php?' . $params_string,
|
||||
'custom_result' => 'IPN OK'
|
||||
'data' => 'https://www.coinpayments.net/index.php?' . $params_string
|
||||
];
|
||||
}
|
||||
|
||||
public function notify($params) {
|
||||
public function notify($params)
|
||||
{
|
||||
|
||||
if (!isset($params['merchant']) || $params['merchant'] != trim($this->config['coinpayments_merchant_id'])) {
|
||||
abort(500, 'No or incorrect Merchant ID passed');
|
||||
@ -75,24 +76,22 @@ class CoinPayments {
|
||||
|
||||
$hmac = hash_hmac("sha512", $request, trim($this->config['coinpayments_ipn_secret']));
|
||||
|
||||
// if (!hash_equals($hmac, $signHeader)) {
|
||||
// if ($hmac != $_SERVER['HTTP_HMAC']) { <-- Use this if you are running a version of PHP below 5.6.0 without the hash_equals function
|
||||
// $this->dieSendMessage(400, 'HMAC signature does not match');
|
||||
// if ($hmac != $signHeader) { <-- Use this if you are running a version of PHP below 5.6.0 without the hash_equals function
|
||||
// abort(400, 'HMAC signature does not match');
|
||||
// }
|
||||
|
||||
if ($hmac != $signHeader) {
|
||||
if (!hash_equals($hmac, $signHeader)) {
|
||||
abort(400, 'HMAC signature does not match');
|
||||
}
|
||||
|
||||
// HMAC Signature verified at this point, load some variables.
|
||||
|
||||
$status = $params['status'];
|
||||
|
||||
if ($status >= 100 || $status == 2) {
|
||||
// payment is complete or queued for nightly payout, success
|
||||
return [
|
||||
'trade_no' => $params['item_number'],
|
||||
'callback_no' => $params['txn_id']
|
||||
'callback_no' => $params['txn_id'],
|
||||
'custom_result' => 'IPN OK'
|
||||
];
|
||||
} else if ($status < 0) {
|
||||
//payment error, this is usually final but payments will sometimes be reopened if there was no exchange rate conversion or with seller consent
|
||||
@ -101,7 +100,5 @@ class CoinPayments {
|
||||
//payment is pending, you can optionally add a note to the order page
|
||||
die('IPN OK: pending');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,9 @@ class RouteServiceProvider extends ServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
if (config('v2board.force_https')) {
|
||||
resolve(\Illuminate\Routing\UrlGenerator::class)->forceScheme('https');
|
||||
}
|
||||
|
||||
parent::boot();
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ use App\Jobs\OrderHandleJob;
|
||||
use App\Models\Order;
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use App\Utils\CacheKey;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class OrderService
|
||||
@ -163,18 +165,17 @@ class OrderService
|
||||
private function getSurplusValueByOneTime(User $user, Order $order)
|
||||
{
|
||||
$lastOneTimeOrder = Order::where('user_id', $user->id)
|
||||
->where('period', 'onetime')
|
||||
->where('period', 'onetime_price')
|
||||
->where('status', 3)
|
||||
->orderBy('id', 'DESC')
|
||||
->first();
|
||||
if (!$lastOneTimeOrder) return;
|
||||
$plan = Plan::find($lastOneTimeOrder->plan_id);
|
||||
if (!$plan) return;
|
||||
$trafficUnitPrice = $plan->onetime_price / $plan->transfer_enable;
|
||||
if ($user->discount && $trafficUnitPrice) {
|
||||
$trafficUnitPrice = $trafficUnitPrice - ($trafficUnitPrice * $user->discount / 100);
|
||||
}
|
||||
$notUsedTraffic = $plan->transfer_enable - (($user->u + $user->d) / 1073741824);
|
||||
$nowUserTraffic = $user->transfer_enable / 1073741824;
|
||||
if (!$nowUserTraffic) return;
|
||||
$paidTotalAmount = ($lastOneTimeOrder->total_amount + $lastOneTimeOrder->balance_amount);
|
||||
if (!$paidTotalAmount) return;
|
||||
$trafficUnitPrice = $paidTotalAmount / $nowUserTraffic;
|
||||
$notUsedTraffic = $nowUserTraffic - (($user->u + $user->d) / 1073741824);
|
||||
$result = $trafficUnitPrice * $notUsedTraffic;
|
||||
$orderModel = Order::where('user_id', $user->id)->where('period', '!=', 'reset_price')->where('status', 3);
|
||||
$order->surplus_amount = $result > 0 ? $result : 0;
|
||||
|
@ -47,7 +47,7 @@ class PaymentService
|
||||
|
||||
return $this->payment->pay([
|
||||
'notify_url' => $notifyUrl,
|
||||
'return_url' => config('v2board.app_url', env('APP_URL')) . '/#/order/' . $order['trade_no'],
|
||||
'return_url' => config('v2board.app_url') . '/#/order/' . $order['trade_no'],
|
||||
'trade_no' => $order['trade_no'],
|
||||
'total_amount' => $order['total_amount'],
|
||||
'user_id' => $order['user_id'],
|
||||
|
@ -14,8 +14,6 @@ use Illuminate\Support\Facades\Cache;
|
||||
class ServerService
|
||||
{
|
||||
|
||||
CONST V2RAY_CONFIG = '{"log":{"loglevel":"debug","access":"access.log","error":"error.log"},"api":{"services":["HandlerService","StatsService"],"tag":"api"},"dns":{},"stats":{},"inbounds":[{"port":443,"protocol":"vmess","settings":{"clients":[]},"sniffing":{"enabled":true,"destOverride":["http","tls"]},"streamSettings":{"network":"tcp"},"tag":"proxy"},{"listen":"127.0.0.1","port":23333,"protocol":"dokodemo-door","settings":{"address":"0.0.0.0"},"tag":"api"}],"outbounds":[{"protocol":"freedom","settings":{}},{"protocol":"blackhole","settings":{},"tag":"block"}],"routing":{"rules":[{"type":"field","inboundTag":"api","outboundTag":"api"}]},"policy":{"levels":{"0":{"handshake":4,"connIdle":300,"uplinkOnly":5,"downlinkOnly":30,"statsUserUplink":true,"statsUserDownlink":true}}}}';
|
||||
CONST TROJAN_CONFIG = '{"run_type":"server","local_addr":"0.0.0.0","local_port":443,"remote_addr":"www.taobao.com","remote_port":80,"password":[],"ssl":{"cert":"server.crt","key":"server.key","sni":"domain.com"},"api":{"enabled":true,"api_addr":"127.0.0.1","api_port":10000}}';
|
||||
public function getV2ray(User $user, $all = false):array
|
||||
{
|
||||
$servers = [];
|
||||
@ -117,153 +115,11 @@ class ServerService
|
||||
->where('banned', 0)
|
||||
->select([
|
||||
'id',
|
||||
'email',
|
||||
't',
|
||||
'u',
|
||||
'd',
|
||||
'transfer_enable',
|
||||
'uuid'
|
||||
])
|
||||
->get();
|
||||
}
|
||||
|
||||
public function getV2RayConfig(int $nodeId, int $localPort)
|
||||
{
|
||||
$server = ServerV2ray::find($nodeId);
|
||||
if (!$server) {
|
||||
abort(500, '节点不存在');
|
||||
}
|
||||
$json = json_decode(self::V2RAY_CONFIG);
|
||||
$json->log->loglevel = (int)config('v2board.server_log_enable') ? 'debug' : 'none';
|
||||
$json->inbounds[1]->port = (int)$localPort;
|
||||
$json->inbounds[0]->port = (int)$server->server_port;
|
||||
$json->inbounds[0]->streamSettings->network = $server->network;
|
||||
$this->setDns($server, $json);
|
||||
$this->setNetwork($server, $json);
|
||||
$this->setRule($server, $json);
|
||||
$this->setTls($server, $json);
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
public function getTrojanConfig(int $nodeId, int $localPort)
|
||||
{
|
||||
$server = ServerTrojan::find($nodeId);
|
||||
if (!$server) {
|
||||
abort(500, '节点不存在');
|
||||
}
|
||||
|
||||
$json = json_decode(self::TROJAN_CONFIG);
|
||||
$json->local_port = $server->server_port;
|
||||
$json->ssl->sni = $server->server_name ? $server->server_name : $server->host;
|
||||
$json->ssl->cert = "/root/.cert/server.crt";
|
||||
$json->ssl->key = "/root/.cert/server.key";
|
||||
$json->api->api_port = $localPort;
|
||||
return $json;
|
||||
}
|
||||
|
||||
private function setDns(ServerV2ray $server, object $json)
|
||||
{
|
||||
if ($server->dnsSettings) {
|
||||
$dns = $server->dnsSettings;
|
||||
if (isset($dns->servers)) {
|
||||
array_push($dns->servers, '1.1.1.1');
|
||||
array_push($dns->servers, 'localhost');
|
||||
}
|
||||
$json->dns = $dns;
|
||||
$json->outbounds[0]->settings->domainStrategy = 'UseIP';
|
||||
}
|
||||
}
|
||||
|
||||
private function setNetwork(ServerV2ray $server, object $json)
|
||||
{
|
||||
if ($server->networkSettings) {
|
||||
switch ($server->network) {
|
||||
case 'tcp':
|
||||
$json->inbounds[0]->streamSettings->tcpSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'kcp':
|
||||
$json->inbounds[0]->streamSettings->kcpSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'ws':
|
||||
$json->inbounds[0]->streamSettings->wsSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'http':
|
||||
$json->inbounds[0]->streamSettings->httpSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'domainsocket':
|
||||
$json->inbounds[0]->streamSettings->dsSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'quic':
|
||||
$json->inbounds[0]->streamSettings->quicSettings = $server->networkSettings;
|
||||
break;
|
||||
case 'grpc':
|
||||
$json->inbounds[0]->streamSettings->grpcSettings = $server->networkSettings;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function setRule(ServerV2ray $server, object $json)
|
||||
{
|
||||
$domainRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_domain')));
|
||||
$protocolRules = array_filter(explode(PHP_EOL, config('v2board.server_v2ray_protocol')));
|
||||
if ($server->ruleSettings) {
|
||||
$ruleSettings = $server->ruleSettings;
|
||||
// domain
|
||||
if (isset($ruleSettings->domain)) {
|
||||
$ruleSettings->domain = array_filter($ruleSettings->domain);
|
||||
if (!empty($ruleSettings->domain)) {
|
||||
$domainRules = array_merge($domainRules, $ruleSettings->domain);
|
||||
}
|
||||
}
|
||||
// protocol
|
||||
if (isset($ruleSettings->protocol)) {
|
||||
$ruleSettings->protocol = array_filter($ruleSettings->protocol);
|
||||
if (!empty($ruleSettings->protocol)) {
|
||||
$protocolRules = array_merge($protocolRules, $ruleSettings->protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($domainRules)) {
|
||||
$domainObj = new \StdClass();
|
||||
$domainObj->type = 'field';
|
||||
$domainObj->domain = $domainRules;
|
||||
$domainObj->outboundTag = 'block';
|
||||
array_push($json->routing->rules, $domainObj);
|
||||
}
|
||||
if (!empty($protocolRules)) {
|
||||
$protocolObj = new \StdClass();
|
||||
$protocolObj->type = 'field';
|
||||
$protocolObj->protocol = $protocolRules;
|
||||
$protocolObj->outboundTag = 'block';
|
||||
array_push($json->routing->rules, $protocolObj);
|
||||
}
|
||||
if (empty($domainRules) && empty($protocolRules)) {
|
||||
$json->inbounds[0]->sniffing->enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private function setTls(ServerV2ray $server, object $json)
|
||||
{
|
||||
if ((int)$server->tls) {
|
||||
$tlsSettings = $server->tlsSettings;
|
||||
$json->inbounds[0]->streamSettings->security = 'tls';
|
||||
$tls = (object)[
|
||||
'certificateFile' => '/root/.cert/server.crt',
|
||||
'keyFile' => '/root/.cert/server.key'
|
||||
];
|
||||
$json->inbounds[0]->streamSettings->tlsSettings = new \StdClass();
|
||||
if (isset($tlsSettings->serverName)) {
|
||||
$json->inbounds[0]->streamSettings->tlsSettings->serverName = (string)$tlsSettings->serverName;
|
||||
}
|
||||
if (isset($tlsSettings->allowInsecure)) {
|
||||
$json->inbounds[0]->streamSettings->tlsSettings->allowInsecure = (int)$tlsSettings->allowInsecure ? true : false;
|
||||
}
|
||||
$json->inbounds[0]->streamSettings->tlsSettings->certificates[0] = $tls;
|
||||
}
|
||||
}
|
||||
|
||||
public function log(int $userId, int $serverId, int $u, int $d, float $rate, string $method)
|
||||
{
|
||||
if (($u + $d) < 10240) return true;
|
||||
|
48
app/Services/ThemeService.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class ThemeService
|
||||
{
|
||||
private $path;
|
||||
private $theme;
|
||||
|
||||
public function __construct($theme)
|
||||
{
|
||||
$this->theme = $theme;
|
||||
$this->path = $path = public_path('theme/');
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
$themeConfigFile = $this->path . "{$this->theme}/config.php";
|
||||
if (!File::exists($themeConfigFile)) return;
|
||||
$themeConfig = include($themeConfigFile);
|
||||
$configs = $themeConfig['configs'];
|
||||
$data = [];
|
||||
foreach ($configs as $config) {
|
||||
$data[$config['field_name']] = isset($config['default_value']) ? $config['default_value'] : '';
|
||||
}
|
||||
|
||||
$data = var_export($data, 1);
|
||||
try {
|
||||
if (!File::put(base_path() . "/config/theme/{$this->theme}.php", "<?php\n return $data ;")) {
|
||||
abort(500, "{$this->theme}初始化失败");
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
abort(500, '请检查V2Board目录权限');
|
||||
}
|
||||
|
||||
try {
|
||||
Artisan::call('config:cache');
|
||||
while (true) {
|
||||
if (config("theme.{$this->theme}")) break;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
abort(500, "{$this->theme}初始化失败");
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,27 @@ use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class TicketService {
|
||||
public function reply($ticket, $message, $userId)
|
||||
{
|
||||
DB::beginTransaction();
|
||||
$ticketMessage = TicketMessage::create([
|
||||
'user_id' => $userId,
|
||||
'ticket_id' => $ticket->id,
|
||||
'message' => $message
|
||||
]);
|
||||
if ($userId !== $ticket->user_id) {
|
||||
$ticket->reply_status = 0;
|
||||
} else {
|
||||
$ticket->reply_status = 1;
|
||||
}
|
||||
if (!$ticketMessage || !$ticket->save()) {
|
||||
DB::rollback();
|
||||
return false;
|
||||
}
|
||||
DB::commit();
|
||||
return $ticketMessage;
|
||||
}
|
||||
|
||||
public function replyByAdmin($ticketId, $message, $userId):void
|
||||
{
|
||||
$ticket = Ticket::where('id', $ticketId)
|
||||
@ -24,7 +45,11 @@ class TicketService {
|
||||
'ticket_id' => $ticket->id,
|
||||
'message' => $message
|
||||
]);
|
||||
$ticket->last_reply_user_id = $userId;
|
||||
if ($userId !== $ticket->user_id) {
|
||||
$ticket->reply_status = 0;
|
||||
} else {
|
||||
$ticket->reply_status = 1;
|
||||
}
|
||||
if (!$ticketMessage || !$ticket->save()) {
|
||||
DB::rollback();
|
||||
abort(500, '工单回复失败');
|
||||
|
@ -15,6 +15,52 @@ use Illuminate\Support\Facades\DB;
|
||||
|
||||
class UserService
|
||||
{
|
||||
public function getResetDay(User $user)
|
||||
{
|
||||
if ($user->expired_at <= time() || $user->expired_at === NULL) return null;
|
||||
// if reset method is not reset
|
||||
if (isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 2) return null;
|
||||
|
||||
if ((int)config('v2board.reset_traffic_method') === 0 ||
|
||||
(isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 0))
|
||||
{
|
||||
$day = date('d', $user->expired_at);
|
||||
$today = date('d');
|
||||
$lastDay = date('d', strtotime('last day of +0 months'));
|
||||
return $lastDay - $today;
|
||||
}
|
||||
if ((int)config('v2board.reset_traffic_method') === 1 ||
|
||||
(isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 1))
|
||||
{
|
||||
$day = date('d', $user->expired_at);
|
||||
$today = date('d');
|
||||
$lastDay = date('d', strtotime('last day of +0 months'));
|
||||
if ((int)$day >= (int)$today && (int)$day >= (int)$lastDay) {
|
||||
return $lastDay - $today;
|
||||
}
|
||||
if ((int)$day >= (int)$today) {
|
||||
return $day - $today;
|
||||
} else {
|
||||
return $lastDay - $today + $day;
|
||||
}
|
||||
}
|
||||
if ((int)config('v2board.reset_traffic_method') === 3 ||
|
||||
(isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 3))
|
||||
{
|
||||
$nextYear = strtotime(date("Y-01-01", strtotime('+1 year')));
|
||||
return (int)(($nextYear - time()) / 86400);
|
||||
}
|
||||
if ((int)config('v2board.reset_traffic_method') === 4 ||
|
||||
(isset($user->plan->reset_traffic_method) && $user->plan->reset_traffic_method === 4))
|
||||
{
|
||||
$md = date('m-d', $user->expired_at);
|
||||
$nowYear = strtotime(date("Y-{$md}"));
|
||||
$nextYear = strtotime('+1 year', $nowYear);
|
||||
return (int)(($nextYear - time()) / 86400);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function isAvailable(User $user)
|
||||
{
|
||||
if (!$user->banned && $user->transfer_enable && ($user->expired_at > time() || $user->expired_at === NULL)) {
|
||||
|
@ -18,7 +18,8 @@ class CacheKey
|
||||
'SERVER_SHADOWSOCKS_LAST_PUSH_AT' => 'ss节点最后推送时间',
|
||||
'TEMP_TOKEN' => '临时令牌',
|
||||
'LAST_SEND_EMAIL_REMIND_TRAFFIC' => '最后发送流量邮件提醒',
|
||||
'SCHEDULE_LAST_CHECK_AT' => '计划任务最后检查时间'
|
||||
'SCHEDULE_LAST_CHECK_AT' => '计划任务最后检查时间',
|
||||
'REGISTER_IP_RATE_LIMIT' => '注册频率限制'
|
||||
];
|
||||
|
||||
public static function get(string $key, $uniqueValue)
|
||||
|
@ -103,14 +103,12 @@ class Helper
|
||||
}
|
||||
}
|
||||
|
||||
public static function getSubscribeHost()
|
||||
public static function getSubscribeUrl($path)
|
||||
{
|
||||
$subscribeUrl = config('v2board.app_url');
|
||||
$subscribeUrls = explode(',', config('v2board.subscribe_url'));
|
||||
if ($subscribeUrls && $subscribeUrls[0]) {
|
||||
$subscribeUrl = $subscribeUrls[rand(0, count($subscribeUrls) - 1)];
|
||||
}
|
||||
return $subscribeUrl;
|
||||
if ($subscribeUrl) return $subscribeUrl . $path;
|
||||
return url($path);
|
||||
}
|
||||
|
||||
public static function randomPort($range) {
|
||||
|
@ -11,26 +11,25 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.2.5|^8.0",
|
||||
"php": "^7.3.0|^8.0",
|
||||
"fideloper/proxy": "^4.4",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"google/recaptcha": "^1.2",
|
||||
"guzzlehttp/guzzle": "^6.3.1|^7.0.1",
|
||||
"laravel/framework": "^7.29",
|
||||
"laravel/horizon": "^4.3.5",
|
||||
"guzzlehttp/guzzle": "^7.4.3",
|
||||
"laravel/framework": "^8.0",
|
||||
"laravel/horizon": "^5.9.6",
|
||||
"laravel/tinker": "^2.5",
|
||||
"linfo/linfo": "^4.0",
|
||||
"lokielse/omnipay-wechatpay": "^3.0",
|
||||
"php-curl-class/php-curl-class": "^8.6",
|
||||
"stripe/stripe-php": "^7.36.1",
|
||||
"symfony/yaml": "^4.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"facade/ignition": "^2.0",
|
||||
"facade/ignition": "^2.3.6",
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"mockery/mockery": "^1.3.1",
|
||||
"nunomaduro/collision": "^4.3",
|
||||
"phpunit/phpunit": "^8.5.8|^9.3.3"
|
||||
"phpunit/phpunit": "^9.0"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
|
@ -237,5 +237,5 @@ return [
|
||||
| The only modification by laravel config
|
||||
|
|
||||
*/
|
||||
'version' => '1.5.5.1646764814759'
|
||||
'version' => '1.5.6.1652111181640'
|
||||
];
|
||||
|
2
config/theme/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.php
|
||||
!.gitignore
|
@ -84,14 +84,14 @@ CREATE TABLE `v2_knowledge` (
|
||||
DROP TABLE IF EXISTS `v2_mail_log`;
|
||||
CREATE TABLE `v2_mail_log` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`email` varchar(64) NOT NULL,
|
||||
`subject` varchar(255) NOT NULL,
|
||||
`template_name` varchar(255) NOT NULL,
|
||||
`error` text,
|
||||
`email` varchar(64) CHARACTER SET utf8 NOT NULL,
|
||||
`subject` varchar(255) CHARACTER SET utf8 NOT NULL,
|
||||
`template_name` varchar(255) CHARACTER SET utf8 NOT NULL,
|
||||
`error` text CHARACTER SET utf8,
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `v2_notice`;
|
||||
@ -101,6 +101,7 @@ CREATE TABLE `v2_notice` (
|
||||
`content` text NOT NULL,
|
||||
`show` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`img_url` varchar(255) DEFAULT NULL,
|
||||
`tags` varchar(255) DEFAULT NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
@ -120,6 +121,7 @@ CREATE TABLE `v2_order` (
|
||||
`trade_no` varchar(36) NOT NULL,
|
||||
`callback_no` varchar(255) DEFAULT NULL,
|
||||
`total_amount` int(11) NOT NULL,
|
||||
`handling_amount` int(11) DEFAULT NULL,
|
||||
`discount_amount` int(11) DEFAULT NULL,
|
||||
`surplus_amount` int(11) DEFAULT NULL COMMENT '剩余价值',
|
||||
`refund_amount` int(11) DEFAULT NULL COMMENT '退款金额',
|
||||
@ -145,6 +147,8 @@ CREATE TABLE `v2_payment` (
|
||||
`icon` varchar(255) DEFAULT NULL,
|
||||
`config` text NOT NULL,
|
||||
`notify_domain` varchar(128) DEFAULT NULL,
|
||||
`handling_fee_fixed` int(11) DEFAULT NULL,
|
||||
`handling_fee_percent` decimal(5,2) DEFAULT NULL,
|
||||
`enable` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sort` int(11) DEFAULT NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
@ -158,11 +162,11 @@ CREATE TABLE `v2_plan` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`group_id` int(11) NOT NULL,
|
||||
`transfer_enable` int(11) NOT NULL,
|
||||
`name` varchar(255) NOT NULL,
|
||||
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
|
||||
`show` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`sort` int(11) DEFAULT NULL,
|
||||
`renew` tinyint(1) NOT NULL DEFAULT '1',
|
||||
`content` text,
|
||||
`content` text CHARACTER SET utf8mb4,
|
||||
`month_price` int(11) DEFAULT NULL,
|
||||
`quarter_price` int(11) DEFAULT NULL,
|
||||
`half_year_price` int(11) DEFAULT NULL,
|
||||
@ -200,6 +204,8 @@ CREATE TABLE `v2_server_shadowsocks` (
|
||||
`port` int(11) NOT NULL,
|
||||
`server_port` int(11) NOT NULL,
|
||||
`cipher` varchar(255) NOT NULL,
|
||||
`obfs` char(11) DEFAULT NULL,
|
||||
`obfs_settings` varchar(255) DEFAULT NULL,
|
||||
`show` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`sort` int(11) DEFAULT NULL,
|
||||
`created_at` int(11) NOT NULL,
|
||||
@ -242,7 +248,6 @@ CREATE TABLE `v2_server_v2ray` (
|
||||
`tags` varchar(255) DEFAULT NULL,
|
||||
`rate` varchar(11) NOT NULL,
|
||||
`network` text NOT NULL,
|
||||
`settings` text,
|
||||
`rules` text,
|
||||
`networkSettings` text,
|
||||
`tlsSettings` text,
|
||||
@ -277,8 +282,8 @@ CREATE TABLE `v2_stat_server` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`server_id` int(11) NOT NULL COMMENT '节点id',
|
||||
`server_type` char(11) NOT NULL COMMENT '节点类型',
|
||||
`u` varchar(255) NOT NULL,
|
||||
`d` varchar(255) NOT NULL,
|
||||
`u` bigint(20) NOT NULL,
|
||||
`d` bigint(20) NOT NULL,
|
||||
`record_type` char(1) NOT NULL COMMENT 'd day m month',
|
||||
`record_at` int(11) NOT NULL COMMENT '记录时间',
|
||||
`created_at` int(11) NOT NULL,
|
||||
@ -294,8 +299,6 @@ DROP TABLE IF EXISTS `v2_stat_user`;
|
||||
CREATE TABLE `v2_stat_user` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`server_id` int(11) NOT NULL,
|
||||
`server_type` char(11) NOT NULL,
|
||||
`server_rate` decimal(10,2) NOT NULL,
|
||||
`u` bigint(20) NOT NULL,
|
||||
`d` bigint(20) NOT NULL,
|
||||
@ -304,9 +307,10 @@ CREATE TABLE `v2_stat_user` (
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `server_id` (`server_id`),
|
||||
UNIQUE KEY `server_rate_user_id_record_at` (`server_rate`,`user_id`,`record_at`),
|
||||
KEY `user_id` (`user_id`),
|
||||
KEY `record_at` (`record_at`)
|
||||
KEY `record_at` (`record_at`),
|
||||
KEY `server_rate` (`server_rate`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
|
||||
@ -314,10 +318,10 @@ DROP TABLE IF EXISTS `v2_ticket`;
|
||||
CREATE TABLE `v2_ticket` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`user_id` int(11) NOT NULL,
|
||||
`last_reply_user_id` int(11) NOT NULL,
|
||||
`subject` varchar(255) NOT NULL,
|
||||
`level` tinyint(1) NOT NULL,
|
||||
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0:已开启 1:已关闭',
|
||||
`reply_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0:待回复 1:已回复',
|
||||
`created_at` int(11) NOT NULL,
|
||||
`updated_at` int(11) NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
@ -362,8 +366,8 @@ CREATE TABLE `v2_user` (
|
||||
`uuid` varchar(36) NOT NULL,
|
||||
`group_id` int(11) DEFAULT NULL,
|
||||
`plan_id` int(11) DEFAULT NULL,
|
||||
`remind_expire` tinyint(4) DEFAULT '0',
|
||||
`remind_traffic` tinyint(4) DEFAULT '0',
|
||||
`remind_expire` tinyint(4) DEFAULT '1',
|
||||
`remind_traffic` tinyint(4) DEFAULT '1',
|
||||
`token` char(32) NOT NULL,
|
||||
`remarks` text,
|
||||
`expired_at` bigint(20) DEFAULT '0',
|
||||
@ -374,4 +378,4 @@ CREATE TABLE `v2_user` (
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
|
||||
-- 2022-03-04 16:25:43
|
||||
-- 2022-06-10 17:12:02
|
||||
|
@ -459,10 +459,6 @@ ALTER TABLE `v2_plan`
|
||||
ALTER TABLE `v2_server`
|
||||
RENAME TO `v2_server_v2ray`;
|
||||
|
||||
ALTER TABLE `v2_user`
|
||||
CHANGE `remind_expire` `remind_expire` tinyint(4) NULL DEFAULT '0' AFTER `plan_id`,
|
||||
CHANGE `remind_traffic` `remind_traffic` tinyint(4) NULL DEFAULT '0' AFTER `remind_expire`;
|
||||
|
||||
ALTER TABLE `v2_payment`
|
||||
ADD `icon` varchar(255) COLLATE 'utf8mb4_general_ci' NULL AFTER `name`;
|
||||
|
||||
@ -513,3 +509,80 @@ ALTER TABLE `v2_stat_user`
|
||||
ADD INDEX `server_id` (`server_id`),
|
||||
ADD INDEX `user_id` (`user_id`),
|
||||
ADD INDEX `record_at` (`record_at`);
|
||||
|
||||
ALTER TABLE `v2_stat_server`
|
||||
CHANGE `u` `u` bigint NOT NULL AFTER `server_type`,
|
||||
CHANGE `d` `d` bigint NOT NULL AFTER `u`;
|
||||
|
||||
ALTER TABLE `v2_payment`
|
||||
ADD `handling_fee_fixed` int(11) NULL AFTER `notify_domain`,
|
||||
ADD `handling_fee_percent` decimal(5,2) NULL AFTER `handling_fee_fixed`;
|
||||
|
||||
ALTER TABLE `v2_order`
|
||||
ADD `handling_amount` int(11) NULL AFTER `total_amount`;
|
||||
|
||||
DELIMITER $$
|
||||
|
||||
DROP PROCEDURE IF EXISTS `path-2022-03-29` $$
|
||||
CREATE PROCEDURE `path-2022-03-29`()
|
||||
BEGIN
|
||||
|
||||
DECLARE IndexIsThere INTEGER;
|
||||
|
||||
SELECT COUNT(1) INTO IndexIsThere
|
||||
FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE table_name = 'v2_stat_user'
|
||||
AND index_name = 'server_id';
|
||||
|
||||
IF IndexIsThere != 0 THEN
|
||||
TRUNCATE TABLE `v2_stat_user`;
|
||||
END IF;
|
||||
|
||||
END $$
|
||||
|
||||
DELIMITER ;
|
||||
CALL `path-2022-03-29`();
|
||||
DROP PROCEDURE IF EXISTS `path-2022-03-29`;
|
||||
|
||||
ALTER TABLE `v2_stat_user`
|
||||
ADD UNIQUE `server_rate_user_id_record_at` (`server_rate`, `user_id`, `record_at`);
|
||||
ALTER TABLE `v2_stat_user`
|
||||
ADD INDEX `server_rate` (`server_rate`);
|
||||
ALTER TABLE `v2_stat_user`
|
||||
DROP INDEX `server_id_user_id_record_at`;
|
||||
ALTER TABLE `v2_stat_user`
|
||||
DROP INDEX `server_id`;
|
||||
|
||||
ALTER TABLE `v2_stat_user`
|
||||
DROP `server_id`;
|
||||
ALTER TABLE `v2_stat_user`
|
||||
DROP `server_type`;
|
||||
|
||||
ALTER TABLE `v2_notice`
|
||||
ADD `tags` varchar(255) COLLATE 'utf8_general_ci' NULL AFTER `img_url`;
|
||||
|
||||
ALTER TABLE `v2_ticket`
|
||||
ADD `reply_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '0:待回复 1:已回复' AFTER `status`;
|
||||
|
||||
ALTER TABLE `v2_server_v2ray`
|
||||
DROP `settings`;
|
||||
|
||||
ALTER TABLE `v2_ticket`
|
||||
DROP `last_reply_user_id`;
|
||||
|
||||
ALTER TABLE `v2_server_shadowsocks`
|
||||
ADD `obfs` char(11) NULL AFTER `cipher`,
|
||||
ADD `obfs_settings` varchar(255) NULL AFTER `obfs`;
|
||||
|
||||
ALTER TABLE `v2_plan`
|
||||
CHANGE `name` `name` varchar(255) COLLATE 'utf8mb4_general_ci' NOT NULL AFTER `transfer_enable`,
|
||||
CHANGE `content` `content` text COLLATE 'utf8mb4_general_ci' NULL AFTER `renew`;
|
||||
|
||||
ALTER TABLE `v2_mail_log`
|
||||
COLLATE 'utf8mb4_general_ci';
|
||||
|
||||
ALTER TABLE `v2_mail_log`
|
||||
CHANGE `email` `email` varchar(64) NOT NULL AFTER `id`,
|
||||
CHANGE `subject` `subject` varchar(255) NOT NULL AFTER `email`,
|
||||
CHANGE `template_name` `template_name` varchar(255) NOT NULL AFTER `subject`,
|
||||
CHANGE `error` `error` text NULL AFTER `template_name`;
|
||||
|
6
init.sh
@ -1,4 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -rf composer.phar
|
||||
wget https://github.com/composer/composer/releases/latest/download/composer.phar -O composer.phar
|
||||
php composer.phar install -vvv
|
||||
php artisan v2board:install
|
||||
|
||||
if [ -f "/etc/init.d/bt" ]; then
|
||||
chown -R www $(pwd);
|
||||
fi
|
||||
|
3
public/assets/admin/env.example.js
vendored
@ -10,5 +10,6 @@ window.settings = {
|
||||
color: 'default'
|
||||
},
|
||||
// 背景
|
||||
background_url: ''
|
||||
background_url: '',
|
||||
logo: ''
|
||||
}
|
||||
|
2
public/assets/admin/umi.css
vendored
2
public/assets/admin/umi.js
vendored
4
public/theme/.gitignore
vendored
@ -1,3 +1,3 @@
|
||||
*
|
||||
!*v2board
|
||||
/*
|
||||
!v2board
|
||||
!.gitignore
|
||||
|
37
public/theme/v2board/assets/i18n/en-US.js
vendored
@ -10,7 +10,7 @@ window.settings.i18n['en-US'] = {
|
||||
'重置流量包': 'Data Reset Package',
|
||||
'待支付': 'Pending Payment',
|
||||
'开通中': 'Pending Active',
|
||||
'已取消': 'Cancelled',
|
||||
'已取消': 'Canceled',
|
||||
'已完成': 'Completed',
|
||||
'已折抵': 'Converted',
|
||||
'待确认': 'Pending',
|
||||
@ -54,7 +54,7 @@ window.settings.i18n['en-US'] = {
|
||||
'佣金将会在确认后会到达你的佣金账户。': 'The commission will reach your commission account after review.',
|
||||
'邀请码管理': 'Invitation Code Management',
|
||||
'生成邀请码': 'Generate invitation code',
|
||||
'邀请明细': 'Invitation Details',
|
||||
'佣金发放记录': 'Commission Income Record',
|
||||
'复制成功': 'Copied successfully',
|
||||
'密码': 'Password',
|
||||
'登入': 'Login',
|
||||
@ -82,8 +82,8 @@ window.settings.i18n['en-US'] = {
|
||||
'等待支付中': 'Waiting for payment',
|
||||
'开通中': 'Pending',
|
||||
'订单系统正在进行处理,请稍等1-3分钟。': 'Order system is being processed, please wait 1 to 3 minutes.',
|
||||
'已取消': 'Cancelled',
|
||||
'订单由于超时支付已被取消。': 'The order has been cancelled due to overtime payment.',
|
||||
'已取消': 'Canceled',
|
||||
'订单由于超时支付已被取消。': 'The order has been canceled due to overtime payment.',
|
||||
'已完成': 'Success',
|
||||
'订单已支付并开通。': 'The order has been paid and the service is activated.',
|
||||
'选择订阅': 'Select a Subscription',
|
||||
@ -96,7 +96,6 @@ window.settings.i18n['en-US'] = {
|
||||
'订单总额': 'Order Total',
|
||||
'下单': 'Order',
|
||||
'总计': 'Total',
|
||||
'订阅变更须知': 'Attention subscription changes',
|
||||
'变更订阅会导致当前订阅被新订阅覆盖,请注意。': 'Attention please, change subscription will overwrite your current subscription.',
|
||||
'该订阅无法续费': 'This subscription cannot be renewed',
|
||||
'选择其他订阅': 'Choose another subscription',
|
||||
@ -239,10 +238,32 @@ window.settings.i18n['en-US'] = {
|
||||
'于 {date} 到期,距离到期还有 {day} 天。': 'Will expire on {date}, {day} days before expiration, ',
|
||||
'Telegram 讨论组': 'Telegram Discussion Group',
|
||||
'立即加入': 'Join Now',
|
||||
'续费': 'Renewal',
|
||||
'购买': 'Purchase',
|
||||
'该订阅无法续费,仅允许新用户购买': 'This subscription cannot be renewed and is only available to new users.',
|
||||
'重置当月流量': 'Reset current month usage',
|
||||
'流量明细仅保留近月数据以供查询。': 'Only keep the most recent month\'s usage for checking the transfer data details.',
|
||||
'扣费倍率': 'Fee deduction rate'
|
||||
'扣费倍率': 'Fee deduction rate',
|
||||
'支付手续费': 'Payment fee',
|
||||
'续费订阅': 'Renewal Subscription',
|
||||
'学习如何使用': 'Learn how to use',
|
||||
'快速将节点导入对应客户端进行使用': 'Quickly export subscription into the client app',
|
||||
'对您当前的订阅进行续费': 'Renew your current subscription',
|
||||
'对您当前的订阅进行购买': 'Purchase your current subscription',
|
||||
'捷径': 'Shortcut',
|
||||
'不会使用,查看使用教程': 'I am a newbie, view the tutorial',
|
||||
'使用支持扫码的客户端进行订阅': 'Use a client app that supports scanning QR code to subscribe',
|
||||
'扫描二维码订阅': 'Scan QR code to subscribe',
|
||||
'续费': 'Renewal',
|
||||
'购买': 'Purchase',
|
||||
'查看教程': 'View Tutorial',
|
||||
'注意': 'Attention',
|
||||
'你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': 'You still have an unpaid order. You need to cancel it before purchasing. Are you sure you want to cancel the previous order?',
|
||||
'确定取消': 'Confirm Cancel',
|
||||
'返回我的订单': 'Back to My Order',
|
||||
'如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': 'If you have already paid, canceling the order may cause the payment to fail. Are you sure you want to cancel the order?',
|
||||
'选择最适合你的计划': 'Choose the right plan for you',
|
||||
'全部': 'All',
|
||||
'按周期': 'By Cycle',
|
||||
'一次性': 'One Time',
|
||||
'遇到问题': 'I have a problem',
|
||||
'遇到问题可以通过工单与我们沟通': 'If you have any problems, you can contact us via ticket'
|
||||
};
|
33
public/theme/v2board/assets/i18n/ja-JP.js
vendored
@ -54,12 +54,12 @@ window.settings.i18n['ja-JP'] = {
|
||||
'佣金将会在确认后会到达你的佣金账户。': 'コミッションは承認処理完了後にカウントされます',
|
||||
'邀请码管理': '招待コードの管理',
|
||||
'生成邀请码': '招待コードを生成',
|
||||
'邀请明细': '招待済みリスト',
|
||||
'佣金发放记录': 'コミッション履歴',
|
||||
'复制成功': 'クリップボードにコピーされました',
|
||||
'密码': 'パスワード',
|
||||
'登入': 'ログイン',
|
||||
'注册': '新規登録',
|
||||
'忘记密码': 'パスワードをお忘れの方は[こちら]',
|
||||
'忘记密码': 'パスワードをお忘れの方',
|
||||
'# 订单号': '受注番号',
|
||||
'周期': 'サイクル',
|
||||
'订单金额': 'ご注文金額',
|
||||
@ -96,7 +96,6 @@ window.settings.i18n['ja-JP'] = {
|
||||
'订单总额': 'ご注文の合計金額',
|
||||
'下单': 'チェックアウト',
|
||||
'总计': '合計',
|
||||
'订阅变更须知': 'プラン変更のご注意',
|
||||
'变更订阅会导致当前订阅被新订阅覆盖,请注意。': 'プランを変更なされます場合は、既存のプランが新規プランによって上書きされます、ご注意下さい',
|
||||
'该订阅无法续费': '該当プランは継続利用できません',
|
||||
'选择其他订阅': 'その他のプランを選択',
|
||||
@ -239,10 +238,32 @@ window.settings.i18n['ja-JP'] = {
|
||||
'于 {date} 到期,距离到期还有 {day} 天。': 'ご利用期限は {date} まで,期限まであと {day} 日',
|
||||
'Telegram 讨论组': 'Telegramグループ',
|
||||
'立即加入': '今すぐ参加',
|
||||
'续费': '継続料金のお支払い',
|
||||
'购买': '購入',
|
||||
'该订阅无法续费,仅允许新用户购买': '該当プランは継続利用できません、新規ユーザーのみが購入可能です',
|
||||
'重置当月流量': '使用済みデータ量のカウントリセット',
|
||||
'流量明细仅保留近月数据以供查询。': 'データ通信明細は当月分のみ表示されます',
|
||||
'扣费倍率': '適応レート'
|
||||
'扣费倍率': '適応レート',
|
||||
'支付手续费': 'お支払い手数料',
|
||||
'续费订阅': '续费订阅',
|
||||
'学习如何使用': '学习如何使用',
|
||||
'快速将节点导入对应客户端进行使用': '快速将节点导入对应客户端进行使用',
|
||||
'对您当前的订阅进行续费': '对您当前的订阅进行续费',
|
||||
'对您当前的订阅进行购买': '对您当前的订阅进行购买',
|
||||
'捷径': '捷径',
|
||||
'不会使用,查看使用教程': '不会使用,查看使用教程',
|
||||
'使用支持扫码的客户端进行订阅': '使用支持扫码的客户端进行订阅',
|
||||
'扫描二维码订阅': '扫描二维码订阅',
|
||||
'续费': '续费',
|
||||
'购买': '购买',
|
||||
'查看教程': '查看教程',
|
||||
'注意': '注意',
|
||||
'你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': '你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?',
|
||||
'确定取消': '确定取消',
|
||||
'返回我的订单': '返回我的订单',
|
||||
'如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': '如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?',
|
||||
'选择最适合你的计划': '选择最适合你的计划',
|
||||
'全部': '全部',
|
||||
'按周期': '按周期',
|
||||
'一次性': '一次性',
|
||||
'遇到问题': '遇到问题',
|
||||
'遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通'
|
||||
};
|
31
public/theme/v2board/assets/i18n/ko-KR.js
vendored
@ -54,7 +54,7 @@ window.settings.i18n['ko-KR'] = {
|
||||
'佣金将会在确认后会到达你的佣金账户。': '수수료는 검토 후 수수료 계정에서 확인할 수 있습니다',
|
||||
'邀请码管理': '초청 코드 관리',
|
||||
'生成邀请码': '초청 코드 생성하기',
|
||||
'邀请明细': '초청 세부사항',
|
||||
'佣金发放记录': '佣金发放记录',
|
||||
'复制成功': '성공적으로 복사 됨',
|
||||
'密码': '비밀번호',
|
||||
'登入': '로그인',
|
||||
@ -96,7 +96,6 @@ window.settings.i18n['ko-KR'] = {
|
||||
'订单总额': '전체 주문',
|
||||
'下单': '주문',
|
||||
'总计': '전체',
|
||||
'订阅变更须知': '구독 변경 사항 주의',
|
||||
'变更订阅会导致当前订阅被新订阅覆盖,请注意。': '주의하십시오. 구독을 변경하면 현재 구독을 덮어씁니다',
|
||||
'该订阅无法续费': '이 구독은 갱신할 수 없습니다.',
|
||||
'选择其他订阅': '다른 구독 선택',
|
||||
@ -239,10 +238,32 @@ window.settings.i18n['ko-KR'] = {
|
||||
'于 {date} 到期,距离到期还有 {day} 天。': '{day}까지, 만료 {day}일 전.',
|
||||
'Telegram 讨论组': '텔레그램으로 문의하세요',
|
||||
'立即加入': '지금 가입하세요',
|
||||
'续费': '고쳐쓰기',
|
||||
'购买': '구매',
|
||||
'该订阅无法续费,仅允许新用户购买': '이 구독은 갱신할 수 없습니다. 신규 사용자만 구매할 수 있습니다.',
|
||||
'重置当月流量': '이번 달 트래픽 초기화',
|
||||
'流量明细仅保留近月数据以供查询。': '귀하의 트래픽 세부 정보는 최근 몇 달 동안만 유지됩니다',
|
||||
'扣费倍率': '수수료 공제율'
|
||||
'扣费倍率': '수수료 공제율',
|
||||
'支付手续费': '支付手续费',
|
||||
'续费订阅': '续费订阅',
|
||||
'学习如何使用': '学习如何使用',
|
||||
'快速将节点导入对应客户端进行使用': '快速将节点导入对应客户端进行使用',
|
||||
'对您当前的订阅进行续费': '对您当前的订阅进行续费',
|
||||
'对您当前的订阅进行购买': '对您当前的订阅进行购买',
|
||||
'捷径': '捷径',
|
||||
'不会使用,查看使用教程': '不会使用,查看使用教程',
|
||||
'使用支持扫码的客户端进行订阅': '使用支持扫码的客户端进行订阅',
|
||||
'扫描二维码订阅': '扫描二维码订阅',
|
||||
'续费': '续费',
|
||||
'购买': '购买',
|
||||
'查看教程': '查看教程',
|
||||
'注意': '注意',
|
||||
'你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': '你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?',
|
||||
'确定取消': '确定取消',
|
||||
'返回我的订单': '返回我的订单',
|
||||
'如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': '如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?',
|
||||
'选择最适合你的计划': '选择最适合你的计划',
|
||||
'全部': '全部',
|
||||
'按周期': '按周期',
|
||||
'一次性': '一次性',
|
||||
'遇到问题': '遇到问题',
|
||||
'遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通'
|
||||
};
|
31
public/theme/v2board/assets/i18n/vi-VN.js
vendored
@ -54,7 +54,7 @@ window.settings.i18n['vi-VN'] = {
|
||||
'佣金将会在确认后会到达你的佣金账户。': 'Sau khi xác nhận tiền hoa hồng sẽ gửi đến tài khoản hoa hồng của bạn.',
|
||||
'邀请码管理': 'Quản lý mã mời',
|
||||
'生成邀请码': 'Tạo mã mời',
|
||||
'邀请明细': 'Chi tiết mời',
|
||||
'佣金发放记录': 'Hồ sơ hoa hồng',
|
||||
'复制成功': 'Sao chép thành công',
|
||||
'密码': 'Mật khẩu',
|
||||
'登入': 'Đăng nhập',
|
||||
@ -96,7 +96,6 @@ window.settings.i18n['vi-VN'] = {
|
||||
'订单总额': 'Tổng tiền đơn hàng',
|
||||
'下单': 'Đặt hàng',
|
||||
'总计': 'Tổng',
|
||||
'订阅变更须知': 'Thông báo thay đổi gói dịch vụ',
|
||||
'变更订阅会导致当前订阅被新订阅覆盖,请注意。': 'Việc thay đổi gói dịch vụ sẽ thay thế gói hiện tại bằng gói mới, xin lưu ý.',
|
||||
'该订阅无法续费': 'Gói này không thể gia hạn',
|
||||
'选择其他订阅': 'Chọn gói dịch vụ khác',
|
||||
@ -239,10 +238,32 @@ window.settings.i18n['vi-VN'] = {
|
||||
'于 {date} 到期,距离到期还有 {day} 天。': 'Hết hạn vào {date}, còn {day} ngày.',
|
||||
'Telegram 讨论组': 'Nhóm Telegram',
|
||||
'立即加入': 'Vào ngay',
|
||||
'续费': 'Gia hạn',
|
||||
'购买': 'Mua',
|
||||
'该订阅无法续费,仅允许新用户购买': 'Đăng ký này không thể gia hạn, chỉ người dùng mới được phép mua',
|
||||
'重置当月流量': 'Đặt lại dung lượng tháng hiện tại',
|
||||
'流量明细仅保留近月数据以供查询。': 'Chi tiết dung lượng chỉ lưu dữ liệu của những tháng gần đây để truy vấn.',
|
||||
'扣费倍率': 'Tỷ lệ khấu trừ'
|
||||
'扣费倍率': 'Tỷ lệ khấu trừ',
|
||||
'支付手续费': 'Phí thủ tục',
|
||||
'续费订阅': '续费订阅',
|
||||
'学习如何使用': '学习如何使用',
|
||||
'快速将节点导入对应客户端进行使用': '快速将节点导入对应客户端进行使用',
|
||||
'对您当前的订阅进行续费': '对您当前的订阅进行续费',
|
||||
'对您当前的订阅进行购买': '对您当前的订阅进行购买',
|
||||
'捷径': '捷径',
|
||||
'不会使用,查看使用教程': '不会使用,查看使用教程',
|
||||
'使用支持扫码的客户端进行订阅': '使用支持扫码的客户端进行订阅',
|
||||
'扫描二维码订阅': '扫描二维码订阅',
|
||||
'续费': '续费',
|
||||
'购买': '购买',
|
||||
'查看教程': '查看教程',
|
||||
'注意': '注意',
|
||||
'你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': '你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?',
|
||||
'确定取消': '确定取消',
|
||||
'返回我的订单': '返回我的订单',
|
||||
'如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': '如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?',
|
||||
'选择最适合你的计划': '选择最适合你的计划',
|
||||
'全部': '全部',
|
||||
'按周期': '按周期',
|
||||
'一次性': '一次性',
|
||||
'遇到问题': '遇到问题',
|
||||
'遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通'
|
||||
};
|
35
public/theme/v2board/assets/i18n/zh-CN.js
vendored
@ -51,10 +51,10 @@ window.settings.i18n['zh-CN'] = {
|
||||
'已注册用户数': '已注册用户数',
|
||||
'佣金比例': '佣金比例',
|
||||
'确认中的佣金': '确认中的佣金',
|
||||
'佣金将会在确认后会到达你的佣金账户。': '佣金将会在确认后会到达您的佣金账户。',
|
||||
'佣金将会在确认后会到达你的佣金账户。': '佣金将会在确认后到达您的佣金账户。',
|
||||
'邀请码管理': '邀请码管理',
|
||||
'生成邀请码': '生成邀请码',
|
||||
'邀请明细': '邀请明细',
|
||||
'佣金发放记录': '佣金发放记录',
|
||||
'复制成功': '复制成功',
|
||||
'密码': '密码',
|
||||
'登入': '登入',
|
||||
@ -96,8 +96,7 @@ window.settings.i18n['zh-CN'] = {
|
||||
'订单总额': '订单总额',
|
||||
'下单': '下单',
|
||||
'总计': '总计',
|
||||
'订阅变更须知': '订阅变更须知',
|
||||
'变更订阅会导致当前订阅被新订阅覆盖,请注意。': '变更订阅会导致当前订阅被新订阅覆盖,请注意。',
|
||||
'变更订阅会导致当前订阅被新订阅覆盖,请注意。': '请注意,变更订阅会导致当前订阅被新订阅覆盖。',
|
||||
'该订阅无法续费': '该订阅无法续费',
|
||||
'选择其他订阅': '选择其它订阅',
|
||||
'我的钱包': '我的钱包',
|
||||
@ -239,10 +238,32 @@ window.settings.i18n['zh-CN'] = {
|
||||
'于 {date} 到期,距离到期还有 {day} 天。': '于 {date} 到期,距离到期还有 {day} 天。',
|
||||
'Telegram 讨论组': 'Telegram 讨论组',
|
||||
'立即加入': '立即加入',
|
||||
'续费': '续费',
|
||||
'购买': '购买',
|
||||
'该订阅无法续费,仅允许新用户购买': '该订阅无法续费,仅允许新用户购买',
|
||||
'重置当月流量': '重置当月流量',
|
||||
'流量明细仅保留近月数据以供查询。': '流量明细仅保留近一个月数据以供查询。',
|
||||
'扣费倍率': '扣费倍率'
|
||||
'扣费倍率': '扣费倍率',
|
||||
'支付手续费': '支付手续费',
|
||||
'续费订阅': '续费订阅',
|
||||
'学习如何使用': '学习如何使用',
|
||||
'快速将节点导入对应客户端进行使用': '快速将节点导入对应客户端进行使用',
|
||||
'对您当前的订阅进行续费': '对您当前的订阅进行续费',
|
||||
'对您当前的订阅进行购买': '对您当前的订阅进行购买',
|
||||
'捷径': '捷径',
|
||||
'不会使用,查看使用教程': '不会使用,查看使用教程',
|
||||
'使用支持扫码的客户端进行订阅': '使用支持扫码的客户端进行订阅',
|
||||
'扫描二维码订阅': '扫描二维码订阅',
|
||||
'续费': '续费',
|
||||
'购买': '购买',
|
||||
'查看教程': '查看教程',
|
||||
'注意': '注意',
|
||||
'你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': '您还有未完成的订单,购买前需要先取消,确定要取消之前的订单吗?',
|
||||
'确定取消': '确定取消',
|
||||
'返回我的订单': '返回我的订单',
|
||||
'如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': '如果您已经付款,取消订单可能会导致支付失败,确定要取消订单吗?',
|
||||
'选择最适合你的计划': '选择最适合您的计划',
|
||||
'全部': '全部',
|
||||
'按周期': '按周期',
|
||||
'一次性': '一次性',
|
||||
'遇到问题': '遇到问题',
|
||||
'遇到问题可以通过工单与我们沟通': '遇到问题可以通过工单与我们沟通'
|
||||
};
|
375
public/theme/v2board/assets/i18n/zh-TW.js
vendored
@ -1,15 +1,15 @@
|
||||
window.settings.i18n['zh-TW'] = {
|
||||
'请求失败': '請求失敗',
|
||||
'月付': '月繳制',
|
||||
'季付': '季付',
|
||||
'半年付': '半年付',
|
||||
'季付': '季繳',
|
||||
'半年付': '半年缴',
|
||||
'年付': '年繳',
|
||||
'两年付': '两年付',
|
||||
'三年付': '三年付',
|
||||
'两年付': '兩年繳',
|
||||
'三年付': '三年繳',
|
||||
'一次性': '一次性',
|
||||
'重置流量包': '重置流量包',
|
||||
'待支付': '待支付',
|
||||
'开通中': '开通中',
|
||||
'开通中': '開通中',
|
||||
'已取消': '已取消',
|
||||
'已完成': '已完成',
|
||||
'已折抵': '已折抵',
|
||||
@ -28,221 +28,242 @@ window.settings.i18n['zh-TW'] = {
|
||||
'我的订单': '我的訂單',
|
||||
'我的邀请': '我的邀請',
|
||||
'用户': '使用者',
|
||||
'我的工单': '我的工单',
|
||||
'流量明细': '流量明细',
|
||||
'我的工单': '我的工單',
|
||||
'流量明细': '流量明細',
|
||||
'使用文档': '說明文件',
|
||||
'绑定Telegram获取更多服务': '绑定Telegram获取更多服务',
|
||||
'点击这里进行绑定': '点击这里进行绑定',
|
||||
'绑定Telegram获取更多服务': '綁定 Telegram 獲取更多服務',
|
||||
'点击这里进行绑定': '點擊這裡進行綁定',
|
||||
'公告': '公告',
|
||||
'总览': '總覽',
|
||||
'该订阅长期有效': '该订阅长期有效',
|
||||
'该订阅长期有效': '該訂閱長期有效',
|
||||
'已过期': '已過期',
|
||||
'已用 {used} / 总计 {total}': '已用 {used} / 总计 {total}',
|
||||
'已用 {used} / 总计 {total}': '已用 {used} / 總計 {total}',
|
||||
'查看订阅': '查看訂閱',
|
||||
'邮箱': '郵箱',
|
||||
'邮箱验证码': '邮箱验证码',
|
||||
'邮箱验证码': '郵箱驗證碼',
|
||||
'发送': '傳送',
|
||||
'重置密码': '重設密碼',
|
||||
'返回登入': '返回登入',
|
||||
'返回登入': '返回登錄',
|
||||
'邀请码': '邀請碼',
|
||||
'复制链接': '複製鏈接',
|
||||
'完成时间': '完成時間',
|
||||
'佣金': '佣金',
|
||||
'已注册用户数': '已注册用户数',
|
||||
'已注册用户数': '已註冊用戶數',
|
||||
'佣金比例': '佣金比例',
|
||||
'确认中的佣金': '确认中的佣金',
|
||||
'佣金将会在确认后会到达你的佣金账户。': '佣金将会在确认后会到达你的佣金账户。',
|
||||
'邀请码管理': '邀请码管理',
|
||||
'生成邀请码': '生成邀请码',
|
||||
'邀请明细': '邀请明细',
|
||||
'确认中的佣金': '確認中的佣金',
|
||||
'佣金将会在确认后会到达你的佣金账户。': '佣金將會在確認後到達您的佣金帳戶。',
|
||||
'邀请码管理': '邀請碼管理',
|
||||
'生成邀请码': '生成邀請碼',
|
||||
'佣金发放记录': '佣金發放記錄',
|
||||
'复制成功': '複製成功',
|
||||
'密码': '密碼',
|
||||
'登入': '登入',
|
||||
'注册': '註冊',
|
||||
'忘记密码': '忘記密碼',
|
||||
'# 订单号': '# 订单号',
|
||||
'# 订单号': '# 訂單號',
|
||||
'周期': '週期',
|
||||
'订单金额': '訂單金額',
|
||||
'订单状态': '訂單狀態',
|
||||
'创建时间': '创建时间',
|
||||
'创建时间': '創建時間',
|
||||
'操作': '操作',
|
||||
'查看详情': '查看详情',
|
||||
'请选择支付方式': '请选择支付方式',
|
||||
'请检查信用卡支付信息': '请检查信用卡支付信息',
|
||||
'订单详情': '订单详情',
|
||||
'查看详情': '查看詳情',
|
||||
'请选择支付方式': '請選擇支付方式',
|
||||
'请检查信用卡支付信息': '請檢查信用卡支付資訊',
|
||||
'订单详情': '訂單詳情',
|
||||
'折扣': '折扣',
|
||||
'折抵': '折抵',
|
||||
'退款': '退款',
|
||||
'支付方式': '支付方式',
|
||||
'填写信用卡支付信息': '填写信用卡支付信息',
|
||||
'您的信用卡信息只会被用作当次扣款,系统并不会保存,这是我们认为最安全的。': '您的信用卡信息只会被用作当次扣款,系统并不会保存,这是我们认为最安全的。',
|
||||
'订单总额': '订单总额',
|
||||
'总计': '总计',
|
||||
'结账': '结账',
|
||||
'填写信用卡支付信息': '填寫信用卡支付資訊',
|
||||
'您的信用卡信息只会被用作当次扣款,系统并不会保存,这是我们认为最安全的。': '您的信用卡資訊只會被用作當次扣款,系統並不會保存,我們認為這是最安全的。',
|
||||
'订单总额': '訂單總額',
|
||||
'总计': '總計',
|
||||
'结账': '結賬',
|
||||
'等待支付中': '等待支付中',
|
||||
'开通中': '开通中',
|
||||
'订单系统正在进行处理,请稍等1-3分钟。': '订单系统正在进行处理,请稍等1-3分钟。',
|
||||
'开通中': '開通中',
|
||||
'订单系统正在进行处理,请稍等1-3分钟。': '訂單系統正在進行處理,請稍等 1-3 分鐘。',
|
||||
'已取消': '已取消',
|
||||
'订单由于超时支付已被取消。': '订单由于超时支付已被取消。',
|
||||
'订单由于超时支付已被取消。': '訂單由於支付超時已被取消',
|
||||
'已完成': '已完成',
|
||||
'订单已支付并开通。': '订单已支付并开通。',
|
||||
'选择订阅': '选择订阅',
|
||||
'立即订阅': '立即订阅',
|
||||
'配置订阅': '配置订阅',
|
||||
'订单已支付并开通。': '訂單已支付並開通',
|
||||
'选择订阅': '選擇訂閱',
|
||||
'立即订阅': '立即訂閱',
|
||||
'配置订阅': '配置訂閱',
|
||||
'折扣': '折扣',
|
||||
'付款周期': '付款周期',
|
||||
'有优惠券?': '有优惠券?',
|
||||
'验证': '验证',
|
||||
'订单总额': '订单总额',
|
||||
'下单': '下单',
|
||||
'总计': '总计',
|
||||
'订阅变更须知': '订阅变更须知',
|
||||
'变更订阅会导致当前订阅被新订阅覆盖,请注意。': '变更订阅会导致当前订阅被新订阅覆盖,请注意。',
|
||||
'该订阅无法续费': '该订阅无法续费',
|
||||
'选择其他订阅': '选择其他订阅',
|
||||
'我的钱包': '我的钱包',
|
||||
'账户余额(仅消费)': '账户余额(仅消费)',
|
||||
'推广佣金(可提现)': '推广佣金(可提现)',
|
||||
'钱包组成部分': '钱包组成部分',
|
||||
'划转': '划转',
|
||||
'推广佣金提现': '推广佣金提现',
|
||||
'修改密码': '修改密码',
|
||||
'保存': '保存',
|
||||
'旧密码': '旧密码',
|
||||
'新密码': '新密码',
|
||||
'请输入旧密码': '请输入旧密码',
|
||||
'请输入新密码': '请输入新密码',
|
||||
'付款周期': '付款週期',
|
||||
'有优惠券?': '有優惠券?',
|
||||
'验证': '驗證',
|
||||
'订单总额': '訂單總額',
|
||||
'下单': '下單',
|
||||
'总计': '總計',
|
||||
'变更订阅会导致当前订阅被新订阅覆盖,请注意。': '請注意,變更訂閱會導致當前訂閱被新訂閱覆蓋。',
|
||||
'该订阅无法续费': '該訂閱無法續費',
|
||||
'选择其他订阅': '選擇其它訂閱',
|
||||
'我的钱包': '我的錢包',
|
||||
'账户余额(仅消费)': '賬戶餘額(僅消費)',
|
||||
'推广佣金(可提现)': '推廣佣金(可提現)',
|
||||
'钱包组成部分': '錢包組成部分',
|
||||
'划转': '劃轉',
|
||||
'推广佣金提现': '推廣佣金提現',
|
||||
'修改密码': '修改密碼',
|
||||
'保存': '儲存',
|
||||
'旧密码': '舊密碼',
|
||||
'新密码': '新密碼',
|
||||
'请输入旧密码': '請輸入舊密碼',
|
||||
'请输入新密码': '請輸入新密碼',
|
||||
'通知': '通知',
|
||||
'到期邮件提醒': '到期邮件提醒',
|
||||
'流量邮件提醒': '流量邮件提醒',
|
||||
'绑定Telegram': '绑定Telegram',
|
||||
'立即开始': '立即开始',
|
||||
'重置订阅信息': '重置订阅信息',
|
||||
'到期邮件提醒': '到期郵件提醒',
|
||||
'流量邮件提醒': '流量郵件提醒',
|
||||
'绑定Telegram': '綁定 Telegram',
|
||||
'立即开始': '立即開始',
|
||||
'重置订阅信息': '重置訂閲資訊',
|
||||
'重置': '重置',
|
||||
'确定要重置订阅信息?': '确定要重置订阅信息?',
|
||||
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。': '如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。',
|
||||
'确定要重置订阅信息?': '確定要重置訂閱資訊?',
|
||||
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。': '如果您的訂閱位址或資訊發生洩露可以執行此操作。重置後您的 UUID 及訂閱將會變更,需要重新導入訂閱。',
|
||||
'重置成功': '重置成功',
|
||||
'两次新密码输入不同': '两次新密码输入不同',
|
||||
'两次密码输入不同': '两次密码输入不同',
|
||||
'两次新密码输入不同': '兩次新密碼輸入不同',
|
||||
'两次密码输入不同': '兩次密碼輸入不同',
|
||||
'邮箱': '郵箱',
|
||||
'邮箱验证码': '邮箱验证码',
|
||||
'发送': '发送',
|
||||
'邀请码': '邀请码',
|
||||
'邀请码(选填)': '邀请码(选填)',
|
||||
'注册': '注册',
|
||||
'返回登入': '返回登入',
|
||||
'我已阅读并同意 <a target="_blank" href="{url}">服务条款</a>': '我已阅读并同意 <a target="_blank" href="{url}">服务条款</a>',
|
||||
'请同意服务条款': '请同意服务条款',
|
||||
'名称': '名称',
|
||||
'标签': '标签',
|
||||
'状态': '状态',
|
||||
'节点五分钟内节点在线情况': '节点五分钟内节点在线情况',
|
||||
'邮箱验证码': '郵箱驗證碼',
|
||||
'发送': '傳送',
|
||||
'邀请码': '邀請碼',
|
||||
'邀请码(选填)': '邀請碼(選填)',
|
||||
'注册': '註冊',
|
||||
'返回登入': '返回登錄',
|
||||
'我已阅读并同意 <a target="_blank" href="{url}">服务条款</a>': '我已閱讀並同意 <a target="_blank" href="{url}">服務條款</a>',
|
||||
'请同意服务条款': '請同意服務條款',
|
||||
'名称': '名稱',
|
||||
'标签': '標籤',
|
||||
'状态': '狀態',
|
||||
'节点五分钟内节点在线情况': '五分鐘內節點線上情況',
|
||||
'倍率': '倍率',
|
||||
'使用的流量将乘以倍率进行扣除': '使用的流量将乘以倍率进行扣除',
|
||||
'使用的流量将乘以倍率进行扣除': '使用的流量將乘以倍率進行扣除',
|
||||
'更多操作': '更多操作',
|
||||
'复制成功': '复制成功',
|
||||
'复制链接': '复制链接',
|
||||
'该订阅长期有效': '该订阅长期有效',
|
||||
'已过期': '已过期',
|
||||
'已用 {used} / 总计 {total}': '已用 {used} / 总计 {total}',
|
||||
'重置订阅信息': '重置订阅信息',
|
||||
'没有可用节点,如果您未订阅或已过期请': '没有可用节点,如果您未订阅或已过期请',
|
||||
'订阅': '订阅',
|
||||
'确定要重置当月流量?': '确定要重置当月流量?',
|
||||
'点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': '点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。',
|
||||
'确定': '确定',
|
||||
'确定要重置订阅信息?': '确定要重置订阅信息?',
|
||||
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。': '如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。',
|
||||
'复制成功': '複製成功',
|
||||
'复制链接': '複製鏈接',
|
||||
'该订阅长期有效': '該訂閱長期有效',
|
||||
'已过期': '已過期',
|
||||
'已用 {used} / 总计 {total}': '已用 {used} / 總計 {total}',
|
||||
'重置订阅信息': '重置訂閲資訊',
|
||||
'没有可用节点,如果您未订阅或已过期请': '沒有可用節點,如果您未訂閱或已過期請',
|
||||
'订阅': '訂閱',
|
||||
'确定要重置当月流量?': '確定要重置當月流量?',
|
||||
'点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': '點擊「確定」將會跳轉到收銀台,支付訂單後系統將會清空您當月已使用流量。',
|
||||
'确定': '確定',
|
||||
'确定要重置订阅信息?': '確定要重置訂閱資訊?',
|
||||
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更,需要重新进行订阅。': '如果您的訂閱位址或資訊發生洩露可以執行此操作。重置後您的 UUID 及訂閱將會變更,需要重新導入訂閱。',
|
||||
'重置成功': '重置成功',
|
||||
'低': '低',
|
||||
'中': '中',
|
||||
'高': '高',
|
||||
'主题': '主题',
|
||||
'工单级别': '工单级别',
|
||||
'工单状态': '工单状态',
|
||||
'最后回复': '最后回复',
|
||||
'已关闭': '已关闭',
|
||||
'待回复': '待回复',
|
||||
'已回复': '已回复',
|
||||
'查看': '查看',
|
||||
'关闭': '关闭',
|
||||
'新的工单': '新的工单',
|
||||
'新的工单': '新的工单',
|
||||
'确认': '确认',
|
||||
'主题': '主题',
|
||||
'请输入工单主题': '请输入工单主题',
|
||||
'工单等级': '工单等级',
|
||||
'请选择工单等级': '请选择工单等级',
|
||||
'消息': '消息',
|
||||
'请描述你遇到的问题': '请描述你遇到的问题',
|
||||
'记录时间': '记录时间',
|
||||
'实际上行': '实际上行',
|
||||
'实际下行': '实际下行',
|
||||
'合计': '合计',
|
||||
'公式:(实际上行 + 实际下行) x 扣费倍率 = 扣除流量': '公式:(实际上行 + 实际下行) x 扣费倍率 = 扣除流量',
|
||||
'复制成功': '复制成功',
|
||||
'复制订阅地址': '复制订阅地址',
|
||||
'主题': '主題',
|
||||
'工单级别': '工單級別',
|
||||
'工单状态': '工單狀態',
|
||||
'最后回复': '最新回復',
|
||||
'已关闭': '已關閉',
|
||||
'待回复': '待回復',
|
||||
'已回复': '已回復',
|
||||
'查看': '檢視',
|
||||
'关闭': '關閉',
|
||||
'新的工单': '新的工單',
|
||||
'新的工单': '新的工單',
|
||||
'确认': '確認',
|
||||
'主题': '主題',
|
||||
'请输入工单主题': '請輸入工單主題',
|
||||
'工单等级': '工單等級',
|
||||
'请选择工单等级': '請選擇工單等級',
|
||||
'消息': '訊息',
|
||||
'请描述你遇到的问题': '請描述您遇到的問題',
|
||||
'记录时间': '記錄時間',
|
||||
'实际上行': '實際上行',
|
||||
'实际下行': '實際下行',
|
||||
'合计': '合計',
|
||||
'公式:(实际上行 + 实际下行) x 扣费倍率 = 扣除流量': '公式:(實際上行 + 實際下行) x 扣費倍率 = 扣除流量',
|
||||
'复制成功': '複製成功',
|
||||
'复制订阅地址': '複製訂閲位址',
|
||||
'导入到': '导入到',
|
||||
'一键订阅': '一键订阅',
|
||||
'复制订阅': '复制订阅',
|
||||
'推广佣金划转至余额': '推广佣金划转至余额',
|
||||
'确认': '确认',
|
||||
'划转后的余额仅用于{title}消费使用': '划转后的余额仅用于{title}消费使用',
|
||||
'当前推广佣金余额': '当前推广佣金余额',
|
||||
'划转金额': '划转金额',
|
||||
'请输入需要划转到余额的金额': '请输入需要划转到余额的金额',
|
||||
'输入内容回复工单...': '输入内容回复工单...',
|
||||
'申请提现': '申请提现',
|
||||
'确认': '确认',
|
||||
'一键订阅': '一鍵訂閲',
|
||||
'复制订阅': '複製訂閲',
|
||||
'推广佣金划转至余额': '推廣佣金劃轉至餘額',
|
||||
'确认': '確認',
|
||||
'划转后的余额仅用于{title}消费使用': '劃轉后的餘額僅用於 {title} 消費使用',
|
||||
'当前推广佣金余额': '當前推廣佣金餘額',
|
||||
'划转金额': '劃轉金額',
|
||||
'请输入需要划转到余额的金额': '請輸入需要劃轉到餘額的金額',
|
||||
'输入内容回复工单...': '輸入内容回復工單…',
|
||||
'申请提现': '申請提現',
|
||||
'确认': '確認',
|
||||
'取消': '取消',
|
||||
'提现方式': '提现方式',
|
||||
'请选择提现方式': '请选择提现方式',
|
||||
'提现账号': '提现账号',
|
||||
'请输入提现账号': '请输入提现账号',
|
||||
'提现方式': '提現方式',
|
||||
'请选择提现方式': '請選擇提現方式',
|
||||
'提现账号': '提現賬號',
|
||||
'请输入提现账号': '請輸入提現賬號',
|
||||
'我知道了': '我知道了',
|
||||
'绑定Telegram': '绑定Telegram',
|
||||
'第一步': '第一步',
|
||||
'第二步': '第二步',
|
||||
'打开Telegram搜索': '打开Telegram搜索',
|
||||
'向机器人发送你的': '向机器人发送你的',
|
||||
'使用文档': '使用文档',
|
||||
'最后更新: {date}': '最后更新: {date}',
|
||||
'复制成功': '复制成功',
|
||||
'我的订阅': '我的订阅',
|
||||
'还有没支付的订单': '还有没支付的订单',
|
||||
'绑定Telegram': '綁定 Telegram',
|
||||
'第一步': '步驟一',
|
||||
'第二步': '步驟二',
|
||||
'打开Telegram搜索': '打開 Telegram 並搜索',
|
||||
'向机器人发送你的': '向機器人發送您的',
|
||||
'使用文档': '使用檔案',
|
||||
'最后更新: {date}': '最後更新: {date}',
|
||||
'复制成功': '複製成功',
|
||||
'我的订阅': '我的訂閱',
|
||||
'还有没支付的订单': '還有未支付的訂單',
|
||||
'立即支付': '立即支付',
|
||||
'条工单正在处理中': '条工单正在处理中',
|
||||
'立即查看': '立即查看',
|
||||
'购买订阅': '购买订阅',
|
||||
'使用文档': '使用文档',
|
||||
'我的订单': '我的订单',
|
||||
'流量明细': '流量明细',
|
||||
'配置订阅': '配置订阅',
|
||||
'我的邀请': '我的邀请',
|
||||
'节点状态': '节点状态',
|
||||
'复制成功': '复制成功',
|
||||
'商品信息': '商品信息',
|
||||
'产品名称': '产品名称',
|
||||
'类型/周期': '类型/周期',
|
||||
'产品流量': '产品流量',
|
||||
'订单信息': '订单信息',
|
||||
'关闭订单': '关闭订单',
|
||||
'订单号': '订单号',
|
||||
'优惠金额': '优惠金额',
|
||||
'旧订阅折抵金额': '旧订阅折抵金额',
|
||||
'退款金额': '退款金额',
|
||||
'余额支付': '余额支付',
|
||||
'我的工单': '我的工单',
|
||||
'工单历史': '工单历史',
|
||||
'{reset_day} 日后重置流量': '{reset_day} 日后重置流量',
|
||||
'节点名称': '节点名称',
|
||||
'于 {date} 到期,距离到期还有 {day} 天。': '于 {date} 到期,距离到期还有 {day} 天。',
|
||||
'Telegram 讨论组': 'Telegram 讨论组',
|
||||
'条工单正在处理中': '條工單正在處理中',
|
||||
'立即查看': '立即檢視',
|
||||
'购买订阅': '購買訂閲',
|
||||
'使用文档': '使用檔案',
|
||||
'我的订单': '我的訂單',
|
||||
'流量明细': '流量明細',
|
||||
'配置订阅': '配置訂閱',
|
||||
'我的邀请': '我的邀請',
|
||||
'节点状态': '節點狀態',
|
||||
'复制成功': '複製成功',
|
||||
'商品信息': '商品資訊',
|
||||
'产品名称': '產品名稱',
|
||||
'类型/周期': '類型/週期',
|
||||
'产品流量': '產品流量',
|
||||
'订单信息': '訂單信息',
|
||||
'关闭订单': '關閉訂單',
|
||||
'订单号': '訂單號',
|
||||
'优惠金额': '優惠金額',
|
||||
'旧订阅折抵金额': '舊訂閲折抵金額',
|
||||
'退款金额': '退款金額',
|
||||
'余额支付': '餘額支付',
|
||||
'我的工单': '我的工單',
|
||||
'工单历史': '工單歷史',
|
||||
'{reset_day} 日后重置流量': '{reset_day} 日後重置流量',
|
||||
'节点名称': '節點名稱',
|
||||
'于 {date} 到期,距离到期还有 {day} 天。': '於 {date} 到期,距離到期還有 {day} 天。',
|
||||
'Telegram 讨论组': 'Telegram 討論組',
|
||||
'立即加入': '立即加入',
|
||||
'续费': '续费',
|
||||
'购买': '购买',
|
||||
'该订阅无法续费,仅允许新用户购买': '该订阅无法续费,仅允许新用户购买',
|
||||
'重置当月流量': '重置当月流量',
|
||||
'流量明细仅保留近月数据以供查询。': '流量明细仅保留近月数据以供查询。',
|
||||
'扣费倍率': '扣费倍率'
|
||||
'该订阅无法续费,仅允许新用户购买': '該訂閲無法續費,僅允許新用戶購買',
|
||||
'重置当月流量': '重置當月流量',
|
||||
'流量明细仅保留近月数据以供查询。': '流量明細僅保留近一個月資料以供查詢。',
|
||||
'扣费倍率': '扣费倍率',
|
||||
'支付手续费': '支付手續費',
|
||||
'续费订阅': '續費訂閲',
|
||||
'学习如何使用': '學習如何使用',
|
||||
'快速将节点导入对应客户端进行使用': '快速將訂閲導入對應的客戶端進行使用',
|
||||
'对您当前的订阅进行续费': '對您的當前訂閲進行續費',
|
||||
'对您当前的订阅进行购买': '重新購買您的當前訂閲',
|
||||
'捷径': '捷徑',
|
||||
'不会使用,查看使用教程': '不會使用,檢視使用檔案',
|
||||
'使用支持扫码的客户端进行订阅': '使用支持掃碼的客戶端進行訂閲',
|
||||
'扫描二维码订阅': '掃描二維碼訂閲',
|
||||
'续费': '續費',
|
||||
'购买': '購買',
|
||||
'查看教程': '查看教程',
|
||||
'注意': '注意',
|
||||
'你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': '您还有未完成的订单,购买前需要先取消,确定要取消之前的订单吗?',
|
||||
'确定取消': '確定取消',
|
||||
'返回我的订单': '返回我的訂單',
|
||||
'如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': '如果您已經付款,取消訂單可能會導致支付失敗,確定要取消訂單嗎?',
|
||||
'选择最适合你的计划': '選擇最適合您的計劃',
|
||||
'全部': '全部',
|
||||
'按周期': '按週期',
|
||||
'一次性': '一次性',
|
||||
'遇到问题': '遇到問題',
|
||||
'遇到问题可以通过工单与我们沟通': '遇到問題您可以通過工單與我們溝通'
|
||||
};
|
BIN
public/theme/v2board/assets/images/icon/Clash For Android.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/theme/v2board/assets/images/icon/Clash For Windows.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/theme/v2board/assets/images/icon/ClashX.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
public/theme/v2board/assets/images/icon/QuantumultX.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
public/theme/v2board/assets/images/icon/Shadowrocket.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
public/theme/v2board/assets/images/icon/Stash.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
public/theme/v2board/assets/images/icon/Surfboard.png
Normal file
After Width: | Height: | Size: 992 B |
BIN
public/theme/v2board/assets/images/icon/Surge.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
3
public/theme/v2board/assets/umi.css
vendored
2
public/theme/v2board/assets/umi.js
vendored
53
public/theme/v2board/config.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'name' => 'V2board',
|
||||
'description' => 'V2board默认主题',
|
||||
'version' => '1.5.6',
|
||||
'images' => 'https://images.unsplash.com/photo-1515405295579-ba7b45403062?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2160&q=80',
|
||||
'configs' => [
|
||||
[
|
||||
'label' => '主题色', // 标签
|
||||
'placeholder' => '请选择主题颜色', // 描述
|
||||
'field_name' => 'theme_color', // 字段名 作为数据key使用
|
||||
'field_type' => 'select', // 字段类型: select,input,switch
|
||||
'select_options' => [ // 当字段类型为select时有效
|
||||
'default' => '默认(蓝色)',
|
||||
'green' => '奶绿色',
|
||||
'black' => '黑色',
|
||||
'darkblue' => '暗蓝色',
|
||||
],
|
||||
'default_value' => 'default' // 字段默认值,将会在首次进行初始化
|
||||
], [
|
||||
'label' => '背景',
|
||||
'placeholder' => '请输入背景图片URL',
|
||||
'field_name' => 'background_url',
|
||||
'field_type' => 'input'
|
||||
], [
|
||||
'label' => '边栏风格',
|
||||
'placeholder' => '请选择边栏风格',
|
||||
'field_name' => 'theme_sidebar',
|
||||
'field_type' => 'select',
|
||||
'select_options' => [
|
||||
'light' => '亮',
|
||||
'dark' => '暗'
|
||||
],
|
||||
'default_value' => 'light'
|
||||
], [
|
||||
'label' => '顶部风格',
|
||||
'placeholder' => '请选择顶部风格',
|
||||
'field_name' => 'theme_header',
|
||||
'field_type' => 'select',
|
||||
'select_options' => [
|
||||
'light' => '亮',
|
||||
'dark' => '暗'
|
||||
],
|
||||
'default_value' => 'dark'
|
||||
], [
|
||||
'label' => '自定义页脚HTML',
|
||||
'placeholder' => '可以实现客服JS代码的加入等',
|
||||
'field_name' => 'custom_html',
|
||||
'field_type' => 'textarea'
|
||||
]
|
||||
]
|
||||
];
|
@ -15,7 +15,7 @@
|
||||
'default' => '#0665d0',
|
||||
'green' => '#319795'
|
||||
])
|
||||
<meta name="theme-color" content="{{$colors[$theme_color]}}">
|
||||
<meta name="theme-color" content="{{$colors[$theme_config['theme_color']]}}">
|
||||
|
||||
<title>{{$title}}</title>
|
||||
<!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Nunito+Sans:300,400,400i,600,700"> -->
|
||||
@ -23,15 +23,15 @@
|
||||
<script>
|
||||
window.settings = {
|
||||
title: '{{$title}}',
|
||||
theme_path: '{{$theme_path}}',
|
||||
theme: {
|
||||
sidebar: '{{$theme_sidebar}}',
|
||||
header: '{{$theme_header}}',
|
||||
color: '{{$theme_color}}',
|
||||
sidebar: '{{$theme_config['theme_sidebar']}}',
|
||||
header: '{{$theme_config['theme_header']}}',
|
||||
color: '{{$theme_config['theme_color']}}',
|
||||
},
|
||||
version: '{{$version}}',
|
||||
background_url: '{{$background_url}}',
|
||||
background_url: '{{$theme_config['background_url']}}',
|
||||
description: '{{$description}}',
|
||||
crisp_id: '{{$crisp_id}}',
|
||||
i18n: [
|
||||
'zh-CN',
|
||||
'en-US',
|
||||
@ -39,7 +39,8 @@
|
||||
'vi-VN',
|
||||
'ko-KR',
|
||||
'zh-TW'
|
||||
]
|
||||
],
|
||||
logo: '{{$logo}}'
|
||||
}
|
||||
</script>
|
||||
<script src="/theme/{{$theme}}/assets/i18n/zh-CN.js?v={{$version}}"></script>
|
||||
@ -52,6 +53,7 @@
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
{!! $theme_config['custom_html'] !!}
|
||||
<script src="/theme/{{$theme}}/assets/vendors.async.js?v={{$version}}"></script>
|
||||
<script src="/theme/{{$theme}}/assets/components.async.js?v={{$version}}"></script>
|
||||
<script src="/theme/{{$theme}}/assets/umi.js?v={{$version}}"></script>
|
||||
|
12
public/vendor/horizon/app-dark.css
vendored
12
public/vendor/horizon/app.css
vendored
2
public/vendor/horizon/app.js
vendored
7
public/vendor/horizon/mix-manifest.json
vendored
@ -1,5 +1,6 @@
|
||||
{
|
||||
"/app.js": "/app.js?id=a2e36b7a4f248973b22b",
|
||||
"/app.css": "/app.css?id=9ce01eaaba790566b895",
|
||||
"/app-dark.css": "/app-dark.css?id=821c845f9bf3b7853c33"
|
||||
"/app.js": "/app.js?id=9db6ba6424a3d1048c194c9c1e4429fe",
|
||||
"/app-dark.css": "/app-dark.css?id=ff172044c4efc9f08f12c0eb824b0226",
|
||||
"/app.css": "/app.css?id=a38514598173eedd6b8575a77bc1ead4",
|
||||
"/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f"
|
||||
}
|
||||
|
@ -86,5 +86,9 @@
|
||||
"The traffic usage in :app_name has reached 80%": "The traffic usage in :app_name has reached 80%",
|
||||
"The service in :app_name is about to expire": "The service in :app_name is about to expire",
|
||||
"The coupon can only be used :limit_use_with_user per person": "The coupon can only be used :limit_use_with_user per person",
|
||||
"The coupon code cannot be used for this period": "The coupon code cannot be used for this period"
|
||||
"The coupon code cannot be used for this period": "The coupon code cannot be used for this period",
|
||||
"Request failed, please try again later": "Request failed, please try again later",
|
||||
"Register frequently, please try again after 1 hour": "Register frequently, please try again after 1 hour",
|
||||
"Uh-oh, we've had some problems, we're working on it.": "Uh-oh, we've had some problems, we're working on it",
|
||||
"This subscription reset package does not apply to your subscription": "This subscription reset package does not apply to your subscription"
|
||||
}
|
||||
|
@ -86,5 +86,9 @@
|
||||
"The traffic usage in :app_name has reached 80%": "在 :app_name 的已用流量已达到 80%",
|
||||
"The service in :app_name is about to expire": "在 :app_name 的服务即将到期",
|
||||
"The coupon can only be used :limit_use_with_user per person": "该优惠券每人只能用 :limit_use_with_user 次",
|
||||
"The coupon code cannot be used for this period": "此优惠券无法用于该付款周期"
|
||||
"The coupon code cannot be used for this period": "此优惠券无法用于该付款周期",
|
||||
"Request failed, please try again later": "请求失败,请稍后再试",
|
||||
"Register frequently, please try again after 1 hour": "注册频繁,请等待1小时后再次尝试",
|
||||
"Uh-oh, we've had some problems, we're working on it.": "遇到了些问题,我们正在进行处理",
|
||||
"This subscription reset package does not apply to your subscription": "该订阅重置包不适用于你的订阅"
|
||||
}
|
||||
|
@ -60,9 +60,6 @@ rules:
|
||||
# - DOMAIN,e.crashlytics.com,REJECT //注释此选项有助于大多数App开发者分析崩溃信息;如果您拒绝一切崩溃数据统计、搜集,请取消 # 注释。
|
||||
|
||||
# 国内网站
|
||||
- DOMAIN-SUFFIX,cn,DIRECT
|
||||
- DOMAIN-KEYWORD,-cn,DIRECT
|
||||
|
||||
- DOMAIN-SUFFIX,126.com,DIRECT
|
||||
- DOMAIN-SUFFIX,126.net,DIRECT
|
||||
- DOMAIN-SUFFIX,127.net,DIRECT
|
||||
@ -366,6 +363,7 @@ rules:
|
||||
- DOMAIN-SUFFIX,klip.me,SELECT
|
||||
- DOMAIN-SUFFIX,libsyn.com,SELECT
|
||||
- DOMAIN-SUFFIX,linkedin.com,SELECT
|
||||
- DOMAIN-SUFFIX,line-apps.com,SELECT
|
||||
- DOMAIN-SUFFIX,linode.com,SELECT
|
||||
- DOMAIN-SUFFIX,lithium.com,SELECT
|
||||
- DOMAIN-SUFFIX,littlehj.com,SELECT
|
||||
@ -550,6 +548,10 @@ rules:
|
||||
- IP-CIDR,224.0.0.0/4,DIRECT
|
||||
- IP-CIDR6,fe80::/10,DIRECT
|
||||
|
||||
# 剩余未匹配的国内网站
|
||||
- DOMAIN-SUFFIX,cn,DIRECT
|
||||
- DOMAIN-KEYWORD,-cn,DIRECT
|
||||
|
||||
# 最终规则
|
||||
- GEOIP,CN,DIRECT
|
||||
- MATCH,SELECT
|
||||
|
@ -24,9 +24,10 @@ dns:
|
||||
- https://doh.pub/dns-query
|
||||
- https://dns.alidns.com/dns-query
|
||||
fallback:
|
||||
- tls://1.0.0.1:853
|
||||
- https://cloudflare-dns.com/dns-query
|
||||
- https://dns.google/dns-query
|
||||
- https://doh.dns.sb/dns-query
|
||||
- https://dns.cloudflare.com/dns-query
|
||||
- https://dns.twnic.tw/dns-query
|
||||
- tls://8.8.4.4:853
|
||||
fallback-filter:
|
||||
geoip: true
|
||||
ipcidr:
|
||||
@ -84,9 +85,6 @@ rules:
|
||||
# - DOMAIN,e.crashlytics.com,REJECT //注释此选项有助于大多数App开发者分析崩溃信息;如果您拒绝一切崩溃数据统计、搜集,请取消 # 注释。
|
||||
|
||||
# 国内网站
|
||||
- DOMAIN-SUFFIX,cn,DIRECT
|
||||
- DOMAIN-KEYWORD,-cn,DIRECT
|
||||
|
||||
- DOMAIN-SUFFIX,126.com,DIRECT
|
||||
- DOMAIN-SUFFIX,126.net,DIRECT
|
||||
- DOMAIN-SUFFIX,127.net,DIRECT
|
||||
@ -390,6 +388,7 @@ rules:
|
||||
- DOMAIN-SUFFIX,klip.me,$app_name
|
||||
- DOMAIN-SUFFIX,libsyn.com,$app_name
|
||||
- DOMAIN-SUFFIX,linkedin.com,$app_name
|
||||
- DOMAIN-SUFFIX,line-apps.com,$app_name
|
||||
- DOMAIN-SUFFIX,linode.com,$app_name
|
||||
- DOMAIN-SUFFIX,lithium.com,$app_name
|
||||
- DOMAIN-SUFFIX,littlehj.com,$app_name
|
||||
@ -574,6 +573,10 @@ rules:
|
||||
- IP-CIDR,224.0.0.0/4,DIRECT
|
||||
- IP-CIDR6,fe80::/10,DIRECT
|
||||
|
||||
# 剩余未匹配的国内网站
|
||||
- DOMAIN-SUFFIX,cn,DIRECT
|
||||
- DOMAIN-KEYWORD,-cn,DIRECT
|
||||
|
||||
# 最终规则
|
||||
- GEOIP,CN,DIRECT
|
||||
- MATCH,$app_name
|
||||
|
@ -78,8 +78,6 @@ DOMAIN-SUFFIX,apple-mapkit.com,DIRECT
|
||||
USER-AGENT,MicroMessenger Client*,DIRECT
|
||||
USER-AGENT,WeChat*,DIRECT
|
||||
|
||||
DOMAIN-SUFFIX,cn,DIRECT
|
||||
DOMAIN-KEYWORD,-cn,DIRECT
|
||||
DOMAIN-SUFFIX,126.com,DIRECT
|
||||
DOMAIN-SUFFIX,126.net,DIRECT
|
||||
DOMAIN-SUFFIX,127.net,DIRECT
|
||||
@ -382,6 +380,7 @@ DOMAIN-SUFFIX,kat.cr,Proxy
|
||||
DOMAIN-SUFFIX,klip.me,Proxy
|
||||
DOMAIN-SUFFIX,libsyn.com,Proxy
|
||||
DOMAIN-SUFFIX,linkedin.com,Proxy
|
||||
DOMAIN-SUFFIX,line-apps.com,Proxy
|
||||
DOMAIN-SUFFIX,linode.com,Proxy
|
||||
DOMAIN-SUFFIX,lithium.com,Proxy
|
||||
DOMAIN-SUFFIX,littlehj.com,Proxy
|
||||
@ -565,6 +564,10 @@ IP-CIDR,100.64.0.0/10,DIRECT
|
||||
IP-CIDR,224.0.0.0/4,DIRECT
|
||||
IP-CIDR6,fe80::/10,DIRECT
|
||||
|
||||
# 剩余未匹配的国内网站
|
||||
DOMAIN-SUFFIX,cn,DIRECT
|
||||
DOMAIN-KEYWORD,-cn,DIRECT
|
||||
|
||||
# 最终规则
|
||||
GEOIP,CN,DIRECT
|
||||
FINAL,Proxy
|
@ -103,8 +103,6 @@ DOMAIN-SUFFIX,apple-mapkit.com,DIRECT
|
||||
USER-AGENT,MicroMessenger Client*,DIRECT
|
||||
USER-AGENT,WeChat*,DIRECT
|
||||
|
||||
DOMAIN-SUFFIX,cn,DIRECT
|
||||
DOMAIN-KEYWORD,-cn,DIRECT
|
||||
DOMAIN-SUFFIX,126.com,DIRECT
|
||||
DOMAIN-SUFFIX,126.net,DIRECT
|
||||
DOMAIN-SUFFIX,127.net,DIRECT
|
||||
@ -407,6 +405,7 @@ DOMAIN-SUFFIX,kat.cr,Proxy
|
||||
DOMAIN-SUFFIX,klip.me,Proxy
|
||||
DOMAIN-SUFFIX,libsyn.com,Proxy
|
||||
DOMAIN-SUFFIX,linkedin.com,Proxy
|
||||
DOMAIN-SUFFIX,line-apps.com,Proxy
|
||||
DOMAIN-SUFFIX,linode.com,Proxy
|
||||
DOMAIN-SUFFIX,lithium.com,Proxy
|
||||
DOMAIN-SUFFIX,littlehj.com,Proxy
|
||||
@ -581,6 +580,10 @@ IP-CIDR,220.181.174.34/32,Proxy,no-resolve
|
||||
|
||||
RULE-SET,LAN,DIRECT
|
||||
|
||||
# 剩余未匹配的国内网站
|
||||
DOMAIN-SUFFIX,cn,DIRECT
|
||||
DOMAIN-KEYWORD,-cn,DIRECT
|
||||
|
||||
# 最终规则
|
||||
GEOIP,CN,DIRECT
|
||||
FINAL,Proxy,dns-failed
|
||||
|
@ -2,9 +2,9 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<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/custom.css?v={{$verison}}">
|
||||
<link rel="stylesheet" href="/assets/admin/components.chunk.css?v={{$version}}">
|
||||
<link rel="stylesheet" href="/assets/admin/umi.css?v={{$version}}">
|
||||
<link rel="stylesheet" href="/assets/admin/custom.css?v={{$version}}">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
|
||||
<title>{{$title}}</title>
|
||||
@ -18,17 +18,18 @@
|
||||
header: '{{$theme_header}}',
|
||||
color: '{{$theme_color}}',
|
||||
},
|
||||
verison: '{{$verison}}',
|
||||
background_url: '{{$backgroun_url}}'
|
||||
version: '{{$version}}',
|
||||
background_url: '{{$background_url}}',
|
||||
logo: '{{$logo}}'
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="/assets/admin/vendors.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/vendors.async.js?v={{$version}}"></script>
|
||||
<script src="/assets/admin/components.async.js?v={{$version}}"></script>
|
||||
<script src="/assets/admin/umi.js?v={{$version}}"></script>
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-P1E9Z5LRRK"></script>
|
||||
<script>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Services\ThemeService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/*
|
||||
@ -22,14 +23,18 @@ Route::get('/', function (Request $request) {
|
||||
$renderParams = [
|
||||
'title' => config('v2board.app_name', 'V2Board'),
|
||||
'theme' => config('v2board.frontend_theme', 'v2board'),
|
||||
'theme_sidebar' => config('v2board.frontend_theme_sidebar', 'light'),
|
||||
'theme_header' => config('v2board.frontend_theme_header', 'dark'),
|
||||
'theme_color' => config('v2board.frontend_theme_color', 'default'),
|
||||
'background_url' => config('v2board.frontend_background_url'),
|
||||
'theme_path' => '/theme/' . config('v2board.frontend_theme', 'v2board') . '/assets/',
|
||||
'version' => config('app.version'),
|
||||
'description' => config('v2board.app_description', 'V2Board is best'),
|
||||
'crisp_id' => config('v2board.frontend_customer_service_method') === 'crisp' ? config('v2board.frontend_customer_service_id') : ''
|
||||
'logo' => config('v2board.logo')
|
||||
];
|
||||
|
||||
if (!config("theme.{$renderParams['theme']}")) {
|
||||
$themeService = new ThemeService($renderParams['theme']);
|
||||
$themeService->init();
|
||||
}
|
||||
|
||||
$renderParams['theme_config'] = config('theme.' . config('v2board.frontend_theme', 'v2board'));
|
||||
return view('theme::' . config('v2board.frontend_theme', 'v2board') . '.dashboard', $renderParams);
|
||||
});
|
||||
|
||||
@ -39,7 +44,8 @@ Route::get('/' . config('v2board.frontend_admin_path', 'admin'), function () {
|
||||
'theme_sidebar' => config('v2board.frontend_theme_sidebar', 'light'),
|
||||
'theme_header' => config('v2board.frontend_theme_header', 'dark'),
|
||||
'theme_color' => config('v2board.frontend_theme_color', 'default'),
|
||||
'backgroun_url' => config('v2board.frontend_background_url'),
|
||||
'verison' => config('app.version')
|
||||
'background_url' => config('v2board.frontend_background_url'),
|
||||
'version' => config('app.version'),
|
||||
'logo' => config('v2board.logo')
|
||||
]);
|
||||
});
|
||||
|