1.7.0
This commit is contained in:
tokumeikoi 2022-12-15 03:31:37 +08:00 committed by GitHub
commit 3e91a7b57a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 797 additions and 240 deletions

View File

@ -80,15 +80,14 @@ class CheckCommission extends Command
public function payHandle($inviteUserId, Order $order)
{
$level = 3;
if ((int)config('v2board.commission_distribution_enable', 0)) {
$level = 3;
$commissionShareLevels = [
0 => (int)config('v2board.commission_distribution_l1'),
1 => (int)config('v2board.commission_distribution_l2'),
2 => (int)config('v2board.commission_distribution_l3')
];
} else {
$level = 3;
$commissionShareLevels = [
0 => 100
];

View File

@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Utils\Helper;
use Illuminate\Console\Command;
class Test extends Command

View File

@ -89,16 +89,17 @@ class V2boardInstall extends Command
while (!$email) {
$email = $this->ask('请输入管理员邮箱?');
}
$password = '';
while (!$password) {
$password = $this->ask('请输入管理员密码?');
}
$password = Helper::guid(false);
if (!$this->registerAdmin($email, $password)) {
abort(500, '管理员账号注册失败,请重试');
}
$this->info('一切就绪');
$this->info('访问 http(s)://你的站点/admin 进入管理面板');
$this->info("管理员邮箱:{$email}");
$this->info("管理员密码:{$password}");
$defaultSecurePath = crc32(config('app.key'));
$this->info("访问 http(s)://你的站点/{$defaultSecurePath} 进入管理面板,你可以用户中心修改你的密码。");
} catch (\Exception $e) {
$this->error($e->getMessage());
}

View File

@ -53,10 +53,10 @@ class V2boardStatistics extends Command
->whereNotIn('status', [0, 2]);
$orderCount = $orderBuilder->count();
$orderAmount = $orderBuilder->sum('total_amount');
$commissionBuilder = Order::where('created_at', '>=', $startAt)
$commissionLogBuilder = CommissionLog::where('created_at', '>=', $startAt)
->where('created_at', '<', $endAt);
$commissionCount = $commissionBuilder->count();
$commissionAmount = $commissionBuilder->sum('actual_commission_balance');
$commissionCount = $commissionLogBuilder->count();
$commissionAmount = $commissionLogBuilder->sum('get_amount');
$data = [
'order_count' => $orderCount,
'order_amount' => $orderAmount,

View File

@ -107,7 +107,8 @@ class ConfigController extends Controller
'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)
'register_limit_expire' => config('v2board.register_limit_expire', 60),
'secure_path' => config('v2board.secure_path', config('v2board.frontend_admin_path', crc32(config('app.key'))))
],
'subscribe' => [
'plan_change_enable' => (int)config('v2board.plan_change_enable', 1),
@ -124,14 +125,11 @@ 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')
],
'server' => [
'server_token' => config('v2board.server_token'),
'server_license' => config('v2board.server_license'),
'server_log_enable' => config('v2board.server_log_enable', 0),
'server_v2ray_domain' => config('v2board.server_v2ray_domain'),
'server_v2ray_protocol' => config('v2board.server_v2ray_protocol'),
'server_pull_interval' => config('v2board.server_pull_interval', 60),
'server_push_interval' => config('v2board.server_push_interval', 60),
],
'email' => [
'email_template' => config('v2board.email_template', 'default'),

View File

@ -41,10 +41,13 @@ class PlanController extends Controller
DB::beginTransaction();
// update user group id and transfer
try {
User::where('plan_id', $plan->id)->update([
'group_id' => $params['group_id'],
'transfer_enable' => $params['transfer_enable'] * 1073741824
]);
if ($request->input('force_update')) {
User::where('plan_id', $plan->id)->update([
'group_id' => $params['group_id'],
'transfer_enable' => $params['transfer_enable'] * 1073741824,
'speed_limit' => $params['speed_limit']
]);
}
$plan->update($params);
} catch (\Exception $e) {
DB::rollBack();

View File

@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers\Admin\Server;
use App\Http\Requests\Admin\ServerShadowsocksSave;
use App\Http\Requests\Admin\ServerShadowsocksUpdate;
use App\Models\ServerRoute;
use App\Models\ServerShadowsocks;
use App\Services\ServerService;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class RouteController extends Controller
{
public function fetch(Request $request)
{
$routes = ServerRoute::get();
return [
'data' => $routes
];
}
public function save(Request $request)
{
$params = $request->validate([
'remarks' => 'required',
'match' => 'required',
'action' => 'required',
'action_value' => 'nullable'
]);
if ($request->input('id')) {
try {
$route = ServerRoute::find($request->input('id'));
$route->update($params);
return [
'data' => true
];
} catch (\Exception $e) {
abort(500, '保存失败');
}
}
if (!ServerRoute::create($params)) abort(500, '创建失败');
return [
'data' => true
];
}
public function drop(Request $request)
{
$route = ServerRoute::find($request->input('id'));
if (!$route) abort(500, '路由不存在');
if (!$route->delete()) abort(500, '删除失败');
return [
'data' => true
];
}
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Admin;
use App\Models\CommissionLog;
use App\Models\ServerShadowsocks;
use App\Models\ServerTrojan;
use App\Models\StatUser;
@ -47,14 +48,12 @@ class StatController extends Controller
->where('created_at', '<', strtotime(date('Y-m-1')))
->whereNotIn('status', [0, 2])
->sum('total_amount'),
'commission_month_payout' => Order::where('actual_commission_balance' ,'!=', NULL)
->where('created_at', '>=', strtotime(date('Y-m-1')))
'commission_month_payout' => CommissionLog::where('created_at', '>=', strtotime(date('Y-m-1')))
->where('created_at', '<', time())
->sum('actual_commission_balance'),
'commission_last_month_payout' => Order::where('actual_commission_balance' ,'!=', NULL)
->where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
->sum('get_amount'),
'commission_last_month_payout' => CommissionLog::where('created_at', '>=', strtotime('-1 month', strtotime(date('Y-m-1'))))
->where('created_at', '<', strtotime(date('Y-m-1')))
->sum('actual_commission_balance'),
->sum('get_amount'),
]
]);
}
@ -100,7 +99,7 @@ class StatController extends Controller
{
$servers = [
'shadowsocks' => ServerShadowsocks::where('parent_id', null)->get()->toArray(),
'vmess' => ServerV2ray::where('parent_id', null)->get()->toArray(),
'v2ray' => ServerV2ray::where('parent_id', null)->get()->toArray(),
'trojan' => ServerTrojan::where('parent_id', null)->get()->toArray()
];
$startAt = strtotime('-1 day', strtotime(date('Y-m-d')));

View File

@ -33,7 +33,14 @@ class AppController extends Controller
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
array_push($proxy, Protocols\Clash::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}

View File

@ -13,9 +13,7 @@ class ClientController extends Controller
public function subscribe(Request $request)
{
$flag = $request->input('flag')
?? (isset($_SERVER['HTTP_USER_AGENT'])
? $_SERVER['HTTP_USER_AGENT']
: '');
?? ($_SERVER['HTTP_USER_AGENT'] ?? '');
$flag = strtolower($flag);
$user = $request->user;
// account not expired and is not banned.

View File

@ -2,6 +2,8 @@
namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Dict;
use phpDocumentor\Reflection\Types\Self_;
use Symfony\Component\Yaml\Yaml;
class Clash
@ -36,7 +38,14 @@ class Clash
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}

View File

@ -2,6 +2,8 @@
namespace App\Http\Controllers\Client\Protocols;
use App\Utils\Helper;
class Shadowrocket
{
public $flag = 'shadowrocket';
@ -43,6 +45,16 @@ class Shadowrocket
public static function buildShadowsocks($password, $server)
{
if ($server['cipher'] === '2022-blake3-aes-128-gcm') {
$serverKey = Helper::getShadowsocksServerKey($server['created_at'], 16);
$userKey = Helper::uuidToBase64($password, 16);
$password = "{$serverKey}:{$userKey}";
}
if ($server['cipher'] === '2022-blake3-aes-256-gcm') {
$serverKey = Helper::getShadowsocksServerKey($server['created_at'], 32);
$userKey = Helper::uuidToBase64($password, 32);
$password = "{$serverKey}:{$userKey}";
}
$name = rawurlencode($server['name']);
$str = str_replace(
['+', '/', '='],

View File

@ -29,7 +29,9 @@ class Shadowsocks
$bytesRemaining = $user['transfer_enable'] - $bytesUsed;
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], ['aes-128-gcm', 'aes-256-gcm', 'aes-192-gcm'])
) {
array_push($configs, self::SIP008($item, $user));
}
}

View File

@ -36,7 +36,14 @@ class Stash
$proxies = [];
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
array_push($proxy, self::buildShadowsocks($user['uuid'], $item));
array_push($proxies, $item['name']);
}

View File

@ -28,7 +28,14 @@ class Surfboard
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
// [Proxy]
$proxies .= self::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]

View File

@ -28,7 +28,14 @@ class Surge
$proxyGroup = '';
foreach ($servers as $item) {
if ($item['type'] === 'shadowsocks') {
if ($item['type'] === 'shadowsocks'
&& in_array($item['cipher'], [
'aes-128-gcm',
'aes-192-gcm',
'aes-256-gcm',
'chacha20-ietf-poly1305'
])
) {
// [Proxy]
$proxies .= self::buildShadowsocks($user['uuid'], $item);
// [Proxy Group]

View File

@ -23,6 +23,19 @@ class CommController extends Controller
'app_description' => config('v2board.app_description'),
'app_url' => config('v2board.app_url'),
'logo' => config('v2board.logo'),
// TODO:REMOVE:1.7.0
'tosUrl' => config('v2board.tos_url'),
'isEmailVerify' => (int)config('v2board.email_verify', 0) ? 1 : 0,
'isInviteForce' => (int)config('v2board.invite_force', 0) ? 1 : 0,
'emailWhitelistSuffix' => (int)config('v2board.email_whitelist_enable', 0)
? $this->getEmailSuffix()
: 0,
'isRecaptcha' => (int)config('v2board.recaptcha_enable', 0) ? 1 : 0,
'recaptchaSiteKey' => config('v2board.recaptcha_site_key'),
'appDescription' => config('v2board.app_description'),
'appUrl' => config('v2board.app_url'),
]
]);
}

View File

@ -7,6 +7,7 @@ use App\Http\Requests\Passport\AuthRegister;
use App\Http\Requests\Passport\AuthForget;
use App\Http\Requests\Passport\AuthLogin;
use App\Jobs\SendEmailJob;
use App\Services\AuthService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use App\Models\Plan;
@ -16,6 +17,7 @@ use App\Utils\Helper;
use App\Utils\Dict;
use App\Utils\CacheKey;
use ReCaptcha\ReCaptcha;
use Firebase\JWT\JWT;
class AuthController extends Controller
{
@ -77,7 +79,9 @@ class AuthController extends Controller
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'));
abort(500, __('Register frequently, please try again after :minute minute', [
'minute' => config('v2board.register_limit_expire', 60)
]));
}
}
if ((int)config('v2board.recaptcha_enable', 0)) {
@ -163,11 +167,6 @@ class AuthController extends Controller
Cache::forget(CacheKey::get('EMAIL_VERIFY_CODE', $request->input('email')));
}
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
$user->last_login_at = time();
$user->save();
@ -178,8 +177,11 @@ class AuthController extends Controller
(int)config('v2board.register_limit_expire', 60) * 60
);
}
$authService = new AuthService($user);
return response()->json([
'data' => $data
'data' => $authService->generateAuthData('register')
]);
}
@ -188,6 +190,12 @@ class AuthController extends Controller
$email = $request->input('email');
$password = $request->input('password');
$passwordErrorCount = (int)Cache::get(CacheKey::get('PASSWORD_ERROR_LIMIT', $email), 0);
if ($passwordErrorCount >= 5) {
abort(500, __('There are too many password errors, please try again after 30 minutes.'));
}
$user = User::where('email', $email)->first();
if (!$user) {
abort(500, __('Incorrect email or password'));
@ -198,6 +206,11 @@ class AuthController extends Controller
$password,
$user->password)
) {
Cache::put(
CacheKey::get('PASSWORD_ERROR_LIMIT', $email),
(int)$passwordErrorCount + 1,
30 * 60
);
abort(500, __('Incorrect email or password'));
}
@ -205,14 +218,9 @@ class AuthController extends Controller
abort(500, __('Your account has been suspended'));
}
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
if ($user->is_admin) $data['is_admin'] = true;
$authService = new AuthService($user);
return response([
'data' => $data
'data' => $authService->generateAuthData('login')
]);
}
@ -241,49 +249,25 @@ class AuthController extends Controller
if ($user->banned) {
abort(500, __('Your account has been suspended'));
}
$data = [
'token' => $user->token,
'auth_data' => base64_encode("{$user->email}:{$user->password}")
];
Cache::forget($key);
$authService = new AuthService($user);
return response([
'data' => $data
'data' => $authService->generateAuthData('token')
]);
}
}
public function getTempToken(Request $request)
{
$user = User::where('token', $request->input('token'))->first();
if (!$user) {
abort(500, __('Token error'));
}
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 60);
return response([
'data' => $code
]);
}
public function getQuickLoginUrl(Request $request)
{
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if (!$authorization) abort(403, '未登录或登陆已过期');
$authData = explode(':', base64_decode($authorization));
if (!isset($authData[0]) || !isset($authData[1])) abort(403, __('Token error'));
$user = User::where('email', $authData[0])
->where('password', $authData[1])
->first();
if (!$user) {
abort(500, __('Token error'));
}
$user = AuthService::decryptAuthData($authorization);
if (!$user) abort(403, '未登录或登陆已过期');
$code = Helper::guid();
$key = CacheKey::get('TEMP_TOKEN', $code);
Cache::put($key, $user->id, 60);
Cache::put($key, $user['id'], 60);
$redirect = '/#/login?verify=' . $code . '&redirect=' . ($request->input('redirect') ? $request->input('redirect') : 'dashboard');
if (config('v2board.app_url')) {
$url = config('v2board.app_url') . $redirect;

View File

@ -84,7 +84,7 @@ class DeepbworkController extends Controller
foreach ($data as $item) {
$u = $item['u'];
$d = $item['d'];
$userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'vmess');
$userService->trafficFetch($u, $d, $item['user_id'], $server->toArray(), 'v2ray');
}
return response([

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Server;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use App\Utils\Helper;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\ServerShadowsocks;
@ -12,12 +13,12 @@ use App\Models\ServerV2ray;
use App\Models\ServerTrojan;
use Illuminate\Support\Facades\Cache;
class VProxyController extends Controller
class UniProxyController extends Controller
{
private $nodeType;
private $nodeInfo;
private $nodeId;
private $token;
private $serverService;
public function __construct(Request $request)
{
@ -28,25 +29,11 @@ class VProxyController extends Controller
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');
}
$this->serverService = new ServerService();
$this->nodeInfo = $this->serverService->getServer($this->nodeId, $this->nodeType);
if (!$this->nodeInfo) abort(500, 'server is not exist');
}
// 后端获取用户
@ -54,21 +41,11 @@ class VProxyController extends Controller
{
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 = $this->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);
@ -78,17 +55,17 @@ class VProxyController extends Controller
}
// 后端提交数据
public function submit(Request $request)
public function push(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'];
$d = $item['d'];
$userService->trafficFetch($u, $d, $item['user_id'], $this->nodeInfo->toArray(), $this->nodeType);
foreach (array_keys($data) as $k) {
$u = $data[$k][0];
$d = $data[$k][1];
$userService->trafficFetch($u, $d, $k, $this->nodeInfo->toArray(), $this->nodeType);
}
return response([
@ -101,28 +78,48 @@ class VProxyController extends Controller
{
switch ($this->nodeType) {
case 'shadowsocks':
die(json_encode([
$response = [
'server_port' => $this->nodeInfo->server_port,
'cipher' => $this->nodeInfo->cipher,
'obfs' => $this->nodeInfo->obfs,
'obfs_settings' => $this->nodeInfo->obfs_settings
], JSON_UNESCAPED_UNICODE));
];
if ($this->nodeInfo->cipher === '2022-blake3-aes-128-gcm') {
$response['server_key'] = Helper::getShadowsocksServerKey($this->nodeInfo->created_at, 16);
}
if ($this->nodeInfo->cipher === '2022-blake3-aes-256-gcm') {
$response['server_key'] = Helper::getShadowsocksServerKey($this->nodeInfo->created_at, 32);
}
break;
case 'v2ray':
die(json_encode([
$response = [
'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([
$response = [
'host' => $this->nodeInfo->host,
'server_port' => $this->nodeInfo->server_port
], JSON_UNESCAPED_UNICODE));
'server_port' => $this->nodeInfo->server_port,
'server_name' => $this->nodeInfo->server_name
];
break;
}
$response['base_config'] = [
'push_interval' => config('v2board.server_push_interval', 60),
'pull_interval' => config('v2board.server_pull_interval', 60)
];
if ($this->nodeInfo['route_id']) {
$response['routes'] = $this->serverService->getRoutes($this->nodeInfo['route_id']);
}
$eTag = sha1(json_encode($response));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
abort(304);
}
return response($response)->header('ETag', "\"{$eTag}\"");
}
}

View File

@ -58,19 +58,21 @@ class InviteController extends Controller
if ($user->commission_rate) {
$commission_rate = $user->commission_rate;
}
$uncheck_commission_balance = (int)Order::where('status', 3)
->where('commission_status', 0)
->where('invite_user_id', $request->user['id'])
->sum('commission_balance');
if (config('v2board.commission_distribution_enable', 0)) {
$uncheck_commission_balance = $uncheck_commission_balance * (config('v2board.commission_distribution_l1') / 100);
}
$stat = [
//已注册用户数
(int)User::where('invite_user_id', $request->user['id'])->count(),
//有效的佣金
(int)Order::where('status', 3)
->where('commission_status', 2)
->where('invite_user_id', $request->user['id'])
->sum('commission_balance'),
(int)CommissionLog::where('invite_user_id', $request->user['id'])
->sum('get_amount'),
//确认中的佣金
(int)Order::where('status', 3)
->where('commission_status', 0)
->where('invite_user_id', $request->user['id'])
->sum('commission_balance'),
$uncheck_commission_balance,
//佣金比例
(int)$commission_rate,
//可用佣金

View File

@ -85,7 +85,7 @@ class OrderController extends Controller
abort(500, __('Subscription plan does not exist'));
}
if (!$planService->haveCapacity() && $request->input('period') !== 'reset_price') {
if ($user->plan_id !== $plan->id && !$planService->haveCapacity() && $request->input('period') !== 'reset_price') {
abort(500, __('Current product is sold out'));
}

View File

@ -26,8 +26,14 @@ class ServerController extends Controller
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
}
$eTag = sha1(json_encode(array_column($servers, 'updated_at')));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {
abort(304);
}
return response([
'data' => $servers
]);
])->header('ETag', "\"{$eTag}\"");
}
}

View File

@ -113,7 +113,8 @@ class UserController extends Controller
'u',
'd',
'transfer_enable',
'email'
'email',
'uuid'
])
->first();
if (!$user) {

View File

@ -2,6 +2,7 @@
namespace App\Http\Middleware;
use App\Services\AuthService;
use Closure;
use Illuminate\Support\Facades\Cache;
@ -19,24 +20,10 @@ class Admin
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if (!$authorization) abort(403, '未登录或登陆已过期');
$authData = explode(':', base64_decode($authorization));
if (!Cache::has($authorization)) {
if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
$user = \App\Models\User::where('password', $authData[1])
->where('email', $authData[0])
->select([
'id',
'email',
'is_admin',
'is_staff'
])
->first();
if (!$user) abort(403, '鉴权失败,请重新登入');
if (!$user->is_admin) abort(403, '鉴权失败,请重新登入');
Cache::put($authorization, $user->toArray(), 3600);
}
$user = AuthService::decryptAuthData($authorization);
if (!$user || !$user['is_admin']) abort(403, '未登录或登陆已过期');
$request->merge([
'user' => Cache::get($authorization)
'user' => $user
]);
return $next($request);
}

View File

@ -2,8 +2,8 @@
namespace App\Http\Middleware;
use App\Services\AuthService;
use Closure;
use Illuminate\Support\Facades\Cache;
class Staff
{
@ -19,24 +19,10 @@ class Staff
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if (!$authorization) abort(403, '未登录或登陆已过期');
$authData = explode(':', base64_decode($authorization));
if (!Cache::has($authorization)) {
if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
$user = \App\Models\User::where('password', $authData[1])
->where('email', $authData[0])
->select([
'id',
'email',
'is_admin',
'is_staff'
])
->first();
if (!$user) abort(403, '鉴权失败,请重新登入');
if (!$user->is_staff) abort(403, '鉴权失败,请重新登入');
Cache::put($authorization, $user->toArray(), 3600);
}
$user = AuthService::decryptAuthData($authorization);
if (!$user || !$user['is_staff']) abort(403, '未登录或登陆已过期');
$request->merge([
'user' => Cache::get($authorization)
'user' => $user
]);
return $next($request);
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Middleware;
use App\Services\AuthService;
use Closure;
use Illuminate\Support\Facades\Cache;
@ -19,23 +20,10 @@ class User
$authorization = $request->input('auth_data') ?? $request->header('authorization');
if (!$authorization) abort(403, '未登录或登陆已过期');
$authData = explode(':', base64_decode($authorization));
if (!Cache::has($authorization)) {
if (!isset($authData[1]) || !isset($authData[0])) abort(403, '鉴权失败,请重新登入');
$user = \App\Models\User::where('password', $authData[1])
->where('email', $authData[0])
->select([
'id',
'email',
'is_admin',
'is_staff'
])
->first();
if (!$user) abort(403, '鉴权失败,请重新登入');
Cache::put($authorization, $user->toArray(), 3600);
}
$user = AuthService::decryptAuthData($authorization);
if (!$user) abort(403, '未登录或登陆已过期');
$request->merge([
'user' => Cache::get($authorization)
'user' => $user
]);
return $next($request);
}

View File

@ -46,6 +46,7 @@ class ConfigSave extends FormRequest
'register_limit_by_ip_enable' => 'in:0,1',
'register_limit_count' => 'integer',
'register_limit_expire' => 'integer',
'secure_path' => '',
// subscribe
'plan_change_enable' => 'in:0,1',
'reset_traffic_method' => 'in:0,1,2,3,4',
@ -56,17 +57,14 @@ class ConfigSave extends FormRequest
'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' => '',
'server_pull_interval' => 'integer',
'server_push_interval' => 'integer',
// frontend
'frontend_theme' => '',
'frontend_theme_sidebar' => 'in:dark,light',
'frontend_theme_header' => 'in:dark,light',
'frontend_theme_color' => 'in:default,darkblue,black,green',
'frontend_theme_sidebar' => 'nullable|in:dark,light',
'frontend_theme_header' => 'nullable|in:dark,light',
'frontend_theme_color' => 'nullable|in:default,darkblue,black,green',
'frontend_background_url' => 'nullable|url',
'frontend_admin_path' => '',
// email
'email_template' => '',
'email_host' => '',

View File

@ -27,7 +27,8 @@ class PlanSave extends FormRequest
'onetime_price' => 'nullable|integer',
'reset_price' => 'nullable|integer',
'reset_traffic_method' => 'nullable|integer|in:0,1,2,3,4',
'capacity_limit' => 'nullable|integer'
'capacity_limit' => 'nullable|integer',
'speed_limit' => 'nullable|integer'
];
}
@ -49,7 +50,8 @@ class PlanSave extends FormRequest
'reset_price.integer' => '流量重置包金额有误',
'reset_traffic_method.integer' => '流量重置方式格式有误',
'reset_traffic_method.in' => '流量重置方式格式有误',
'capacity_limit.integer' => '容纳用户量限制格式有误'
'capacity_limit.integer' => '容纳用户量限制格式有误',
'speed_limit.integer' => '限速格式有误'
];
}
}

View File

@ -18,10 +18,11 @@ class ServerShadowsocksSave extends FormRequest
'name' => 'required',
'group_id' => 'required|array',
'parent_id' => 'nullable|integer',
'route_id' => 'nullable|array',
'host' => 'required',
'port' => 'required',
'server_port' => 'required',
'cipher' => 'required|in:aes-128-gcm,aes-256-gcm,chacha20-ietf-poly1305',
'cipher' => 'required|in:aes-128-gcm,aes-192-gcm,aes-256-gcm,chacha20-ietf-poly1305,2022-blake3-aes-128-gcm,2022-blake3-aes-256-gcm',
'obfs' => 'nullable|in:http',
'obfs_settings' => 'nullable|array',
'tags' => 'nullable|array',
@ -35,6 +36,7 @@ class ServerShadowsocksSave extends FormRequest
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'route_id.array' => '路由组格式不正确',
'parent_id.integer' => '父节点格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',

View File

@ -17,6 +17,7 @@ class ServerTrojanSave extends FormRequest
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'route_id' => 'nullable|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
@ -34,6 +35,7 @@ class ServerTrojanSave extends FormRequest
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'route_id.array' => '路由组格式不正确',
'parent_id.integer' => '父节点格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',

View File

@ -17,6 +17,7 @@ class ServerV2raySave extends FormRequest
'show' => '',
'name' => 'required',
'group_id' => 'required|array',
'route_id' => 'nullable|array',
'parent_id' => 'nullable|integer',
'host' => 'required',
'port' => 'required',
@ -38,6 +39,7 @@ class ServerV2raySave extends FormRequest
'name.required' => '节点名称不能为空',
'group_id.required' => '权限组不能为空',
'group_id.array' => '权限组格式不正确',
'route_id.array' => '路由组格式不正确',
'parent_id.integer' => '父ID格式不正确',
'host.required' => '节点地址不能为空',
'port.required' => '连接端口不能为空',

View File

@ -14,7 +14,7 @@ class UserFetch extends FormRequest
public function rules()
{
return [
'filter.*.key' => 'required|in:id,email,transfer_enable,d,expired_at,uuid,token,invite_by_email,invite_user_id,plan_id,banned,remarks',
'filter.*.key' => 'required|in:id,email,transfer_enable,d,expired_at,uuid,token,invite_by_email,invite_user_id,plan_id,banned,remarks,is_admin',
'filter.*.condition' => 'required|in:>,<,=,>=,<=,模糊,!=',
'filter.*.value' => 'required'
];

View File

@ -29,7 +29,8 @@ class UserUpdate extends FormRequest
'balance' => 'integer',
'commission_type' => 'integer',
'commission_balance' => 'integer',
'remarks' => 'nullable'
'remarks' => 'nullable',
'speed_limit' => 'nullable|integer'
];
}
@ -59,7 +60,8 @@ class UserUpdate extends FormRequest
'd.integer' => '下行流量格式不正确',
'balance.integer' => '余额格式不正确',
'commission_balance.integer' => '佣金格式不正确',
'password.min' => '密码长度最小8位'
'password.min' => '密码长度最小8位',
'speed_limit.integer' => '限速格式不正确'
];
}
}

View File

@ -8,7 +8,7 @@ class AdminRoute
public function map(Registrar $router)
{
$router->group([
'prefix' => 'admin',
'prefix' => config('v2board.secure_path', config('v2board.frontend_admin_path', 'admin')),
'middleware' => 'admin'
], function ($router) {
// Config
@ -28,6 +28,9 @@ class AdminRoute
$router->get ('/server/group/fetch', 'Admin\\Server\\GroupController@fetch');
$router->post('/server/group/save', 'Admin\\Server\\GroupController@save');
$router->post('/server/group/drop', 'Admin\\Server\\GroupController@drop');
$router->get ('/server/route/fetch', 'Admin\\Server\\RouteController@fetch');
$router->post('/server/route/save', 'Admin\\Server\\RouteController@save');
$router->post('/server/route/drop', 'Admin\\Server\\RouteController@drop');
$router->get ('/server/manage/getNodes', 'Admin\\Server\\ManageController@getNodes');
$router->post('/server/manage/sort', 'Admin\\Server\\ManageController@sort');
$router->group([

View File

@ -14,7 +14,6 @@ class ClientRoute
// Client
$router->get('/subscribe', 'Client\\ClientController@subscribe');
// App
$router->get('/app/config', 'Client\\AppController@config');
$router->get('/app/getConfig', 'Client\\AppController@getConfig');
$router->get('/app/getVersion', 'Client\\AppController@getVersion');
});

View File

@ -20,6 +20,7 @@ class PassportRoute
// Comm
$router->post('/comm/sendEmailVerify', 'Passport\\CommController@sendEmailVerify');
$router->post('/comm/pv', 'Passport\\CommController@pv');
$router->get ('/comm/config', 'Guest\\CommController@config'); // TODO:REMOVE:1.7.0
});
}
}

16
app/Models/ServerRoute.php Executable file
View File

@ -0,0 +1,16 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class ServerRoute extends Model
{
protected $table = 'v2_server_route';
protected $dateFormat = 'U';
protected $guarded = ['id'];
protected $casts = [
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
];
}

View File

@ -13,6 +13,7 @@ class ServerShadowsocks extends Model
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'route_id' => 'array',
'tags' => 'array',
'obfs_settings' => 'array'
];

View File

@ -13,6 +13,7 @@ class ServerTrojan extends Model
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'route_id' => 'array',
'tags' => 'array'
];
}

View File

@ -13,6 +13,7 @@ class ServerV2ray extends Model
'created_at' => 'timestamp',
'updated_at' => 'timestamp',
'group_id' => 'array',
'route_id' => 'array',
'tlsSettings' => 'array',
'networkSettings' => 'array',
'dnsSettings' => 'array',

View File

@ -32,6 +32,11 @@ class MGate {
'label' => 'AppSecret',
'description' => '',
'type' => 'input',
],
'mgate_source_currency' => [
'label' => '源货币',
'description' => '默认CNY',
'type' => 'input'
]
];
}
@ -44,6 +49,9 @@ class MGate {
'notify_url' => $order['notify_url'],
'return_url' => $order['return_url']
];
if (isset($this->config['mgate_source_currency'])) {
$params['source_currency'] = $this->config['mgate_source_currency'];
}
$params['app_id'] = $this->config['mgate_app_id'];
ksort($params);
$str = http_build_query($params) . $this->config['mgate_app_secret'];

View File

@ -0,0 +1,54 @@
<?php
namespace App\Services;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
class AuthService
{
private $user;
public function __construct($user)
{
$this->user = $user;
}
public function generateAuthData($utm)
{
return [
'token' => $this->user->token,
'is_admin' => $this->user->is_admin,
'auth_data' => JWT::encode([
'expired_at' => time() + 3600,
'id' => $this->user->id,
'utm' => $utm,
], config('app.key'), 'HS256')
];
}
public static function decryptAuthData($jwt)
{
try {
if (!Cache::has($jwt)) {
$data = (array)JWT::decode($jwt, new Key(config('app.key'), 'HS256'));
if ($data['expired_at'] < time()) return false;
$user = User::select([
'id',
'email',
'is_admin',
'is_staff'
])
->find($data['id']);
if (!$user) return false;
Cache::put($jwt, $user->toArray(), 3600);
}
return Cache::get($jwt);
} catch (\Exception $e) {
return false;
}
}
}

View File

@ -71,6 +71,8 @@ class OrderService
break;
}
$this->setSpeedLimit($plan->speed_limit);
if (!$this->user->save()) {
DB::rollBack();
abort(500, '开通失败');
@ -253,6 +255,11 @@ class OrderService
return true;
}
private function setSpeedLimit($speedLimit)
{
$this->user->speed_limit = $speedLimit;
}
private function buyByResetTraffic()
{
$this->user->u = 0;

View File

@ -3,6 +3,7 @@
namespace App\Services;
use App\Models\ServerLog;
use App\Models\ServerRoute;
use App\Models\ServerShadowsocks;
use App\Models\User;
use App\Models\ServerV2ray;
@ -100,10 +101,13 @@ class ServerService
);
$tmp = array_column($servers, 'sort');
array_multisort($tmp, SORT_ASC, $servers);
$servers = array_map(function ($server) {
$server['port'] = (int)$server['port'];
return $server;
}, $servers);
return $servers;
}
public function getAvailableUsers($groupId)
{
return User::whereIn('group_id', $groupId)
@ -179,7 +183,7 @@ class ServerService
return $server->toArray();
}
public function mergeData(&$servers)
private function mergeData(&$servers)
{
foreach ($servers as $k => $v) {
$serverType = strtoupper($servers[$k]['type']);
@ -213,4 +217,23 @@ class ServerService
array_multisort($tmp, SORT_ASC, $servers);
return $servers;
}
public function getRoutes(array $routeIds)
{
return ServerRoute::select(['id', 'match', 'action', 'action_value'])->whereIn('id', $routeIds)->get();
}
public function getServer($serverId, $serverType)
{
switch ($serverType) {
case 'v2ray':
return ServerV2ray::find($serverId);
case 'shadowsocks':
return ServerShadowsocks::find($serverId);
case 'trojan':
return ServerTrojan::find($serverId);
default:
return false;
}
}
}

View File

@ -20,7 +20,8 @@ class CacheKey
'LAST_SEND_EMAIL_REMIND_TRAFFIC' => '最后发送流量邮件提醒',
'SCHEDULE_LAST_CHECK_AT' => '计划任务最后检查时间',
'REGISTER_IP_RATE_LIMIT' => '注册频率限制',
'LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP' => '最后一次发送登入链接时间'
'LAST_SEND_LOGIN_WITH_MAIL_LINK_TIMESTAMP' => '最后一次发送登入链接时间',
'PASSWORD_ERROR_LIMIT' => '密码错误次数限制'
];
public static function get(string $key, $uniqueValue)

View File

@ -4,6 +4,16 @@ namespace App\Utils;
class Helper
{
public static function uuidToBase64($uuid, $length)
{
return base64_encode(substr($uuid, 0, $length));
}
public static function getShadowsocksServerKey($timestamp, $length)
{
return base64_encode(substr(md5($timestamp), 0, $length));
}
public static function guid($format = false)
{
if (function_exists('com_create_guid') === true) {

View File

@ -13,6 +13,7 @@
"require": {
"php": "^7.3.0|^8.0",
"fideloper/proxy": "^4.4",
"firebase/php-jwt": "^6.3",
"fruitcake/laravel-cors": "^2.0",
"google/recaptcha": "^1.2",
"guzzlehttp/guzzle": "^7.4.3",

View File

@ -237,5 +237,5 @@ return [
| The only modification by laravel config
|
*/
'version' => '1.6.1.1665920414108'
'version' => '1.7.0'
];

View File

@ -162,6 +162,7 @@ CREATE TABLE `v2_plan` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`transfer_enable` int(11) NOT NULL,
`speed_limit` int(11) DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`show` tinyint(1) NOT NULL DEFAULT '0',
`sort` int(11) DEFAULT NULL,
@ -193,16 +194,30 @@ CREATE TABLE `v2_server_group` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `v2_server_route`;
CREATE TABLE `v2_server_route` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`remarks` varchar(255) NOT NULL,
`match` varchar(255) NOT NULL,
`action` varchar(11) NOT NULL,
`action_value` varchar(255) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
DROP TABLE IF EXISTS `v2_server_shadowsocks`;
CREATE TABLE `v2_server_shadowsocks` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL,
`route_id` varchar(255) DEFAULT NULL,
`parent_id` int(11) DEFAULT NULL,
`tags` varchar(255) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`rate` varchar(11) NOT NULL,
`host` varchar(255) NOT NULL,
`port` int(11) NOT NULL,
`port` varchar(11) NOT NULL,
`server_port` int(11) NOT NULL,
`cipher` varchar(255) NOT NULL,
`obfs` char(11) DEFAULT NULL,
@ -219,12 +234,13 @@ DROP TABLE IF EXISTS `v2_server_trojan`;
CREATE TABLE `v2_server_trojan` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '节点ID',
`group_id` varchar(255) NOT NULL COMMENT '节点组',
`route_id` varchar(255) DEFAULT NULL,
`parent_id` int(11) DEFAULT NULL COMMENT '父节点',
`tags` varchar(255) DEFAULT NULL COMMENT '节点标签',
`name` varchar(255) NOT NULL COMMENT '节点名称',
`rate` varchar(11) NOT NULL COMMENT '倍率',
`host` varchar(255) NOT NULL COMMENT '主机名',
`port` int(11) NOT NULL COMMENT '连接端口',
`port` varchar(11) NOT NULL COMMENT '连接端口',
`server_port` int(11) NOT NULL COMMENT '服务端口',
`allow_insecure` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否允许不安全',
`server_name` varchar(255) DEFAULT NULL,
@ -240,10 +256,11 @@ DROP TABLE IF EXISTS `v2_server_v2ray`;
CREATE TABLE `v2_server_v2ray` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` varchar(255) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 NOT NULL,
`route_id` varchar(255) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`parent_id` int(11) DEFAULT NULL,
`host` varchar(255) NOT NULL,
`port` char(11) NOT NULL,
`port` varchar(11) NOT NULL,
`server_port` int(11) NOT NULL,
`tls` tinyint(4) NOT NULL DEFAULT '0',
`tags` varchar(255) DEFAULT NULL,
@ -259,7 +276,7 @@ CREATE TABLE `v2_server_v2ray` (
`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_stat_order`;
@ -367,6 +384,7 @@ CREATE TABLE `v2_user` (
`uuid` varchar(36) NOT NULL,
`group_id` int(11) DEFAULT NULL,
`plan_id` int(11) DEFAULT NULL,
`speed_limit` int(11) DEFAULT NULL,
`remind_expire` tinyint(4) DEFAULT '1',
`remind_traffic` tinyint(4) DEFAULT '1',
`token` char(32) NOT NULL,
@ -379,4 +397,4 @@ CREATE TABLE `v2_user` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- 2022-07-07 18:23:17
-- 2022-11-27 07:09:04

View File

@ -587,8 +587,55 @@ ALTER TABLE `v2_mail_log`
CHANGE `template_name` `template_name` varchar(255) NOT NULL AFTER `subject`,
CHANGE `error` `error` text NULL AFTER `template_name`;
ALTER TABLE `v2_plan`
ADD `inventory_limit` int(11) NULL AFTER `reset_traffic_method`;
ALTER TABLE `v2_user`
ADD `speed_limit` int(11) NULL AFTER `plan_id`;
ALTER TABLE `v2_plan`
CHANGE `inventory_limit` `capacity_limit` int(11) NULL AFTER `reset_traffic_method`;
ADD `speed_limit` int(11) NULL AFTER `transfer_enable`;
ALTER TABLE `v2_server_v2ray`
CHANGE `port` `port` varchar(11) COLLATE 'utf8_general_ci' NOT NULL AFTER `host`;
ALTER TABLE `v2_server_shadowsocks`
CHANGE `port` `port` varchar(11) NOT NULL AFTER `host`;
ALTER TABLE `v2_server_trojan`
CHANGE `port` `port` varchar(11) NOT NULL COMMENT '连接端口' AFTER `host`;
DELETE FROM `v2_stat_server`
WHERE `server_type` = 'vmess';
ALTER TABLE `v2_server_shadowsocks`
ADD `route_id` varchar(255) COLLATE 'utf8mb4_general_ci' NULL AFTER `group_id`;
ALTER TABLE `v2_server_trojan`
ADD `route_id` varchar(255) COLLATE 'utf8mb4_general_ci' NULL AFTER `group_id`;
ALTER TABLE `v2_server_v2ray`
COLLATE 'utf8mb4_general_ci';
ALTER TABLE `v2_server_v2ray`
CHANGE `group_id` `group_id` varchar(255) NOT NULL AFTER `id`,
CHANGE `route_id` `route_id` varchar(255) NULL AFTER `group_id`,
CHANGE `host` `host` varchar(255) NOT NULL AFTER `parent_id`,
CHANGE `port` `port` varchar(11) NOT NULL AFTER `host`,
CHANGE `tags` `tags` varchar(255) NULL AFTER `tls`,
CHANGE `rate` `rate` varchar(11) NOT NULL AFTER `tags`,
CHANGE `network` `network` text NOT NULL AFTER `rate`,
CHANGE `rules` `rules` text NULL AFTER `network`,
CHANGE `networkSettings` `networkSettings` text NULL AFTER `rules`,
CHANGE `tlsSettings` `tlsSettings` text NULL AFTER `networkSettings`,
CHANGE `ruleSettings` `ruleSettings` text NULL AFTER `tlsSettings`,
CHANGE `dnsSettings` `dnsSettings` text NULL AFTER `ruleSettings`;
ALTER TABLE `v2_server_v2ray`
ADD `route_id` varchar(255) COLLATE 'utf8mb4_general_ci' NULL AFTER `group_id`;
CREATE TABLE `v2_server_route` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`remarks` varchar(255) NOT NULL,
`match` varchar(255) NOT NULL,
`action` varchar(11) NOT NULL,
`action_value` varchar(255) DEFAULT NULL,
`created_at` int(11) NOT NULL,
`updated_at` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

File diff suppressed because one or more lines are too long

View File

@ -11,5 +11,7 @@ window.settings = {
},
// 背景
background_url: '',
logo: ''
logo: '',
// 需与V2Board设置中的后台路径一致
secure_path: 'admin'
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -21,6 +21,7 @@ window.settings = {
'ja-JP',
'vi-VN',
'ko-KR',
'zh-TW'
'zh-TW',
'fa-IR'
]
}

View File

@ -0,0 +1,277 @@
window.settings.i18n['fa-IR'] = {
'请求失败': 'درخواست انجام نشد',
'月付': 'ماهانه',
'季付': 'سه ماهه',
'半年付': 'نیم سال',
'年付': 'سالانه',
'两年付': 'دو سال',
'三年付': 'سه سال',
'一次性': 'یک‌باره',
'重置流量包': 'بازنشانی بسته های داده',
'待支付': 'در انتظار پرداخت',
'开通中': 'ایجاید',
'已取消': 'صرف نظر شد',
'已完成': 'به پایان رسید',
'已折抵': 'تخفیف داده شده است',
'待确认': 'در حال بررسی',
'发放中': 'صدور',
'已发放': 'صادر شده',
'无效': 'نامعتبر',
'个人中心': 'پروفایل',
'登出': 'خروج',
'搜索': 'جستجو',
'仪表盘': 'داشبرد',
'订阅': 'اشتراک',
'我的订阅': 'اشتراک من',
'购买订阅': 'خرید اشتراک',
'财务': 'امور مالی',
'我的订单': 'درخواست های من',
'我的邀请': 'دعوتنامه های من',
'用户': 'کاربر',
'我的工单': 'درخواست های من',
'流量明细': 'جزئیات\nعبورو مرور در\nمحیط آموزشی',
'使用文档': 'کار با مستندات',
'绑定Telegram获取更多服务': 'برای خدمات بیشتر تلگرام را ببندید',
'点击这里进行绑定': 'برای اتصال اینجا را کلیک کنید',
'公告': 'هشدارها',
'总览': 'بررسی کلی',
'该订阅长期有效': 'این اشتراک برای مدت طولانی معتبر است',
'已过期': 'منقضی شده',
'已用 {used} / 总计 {total}': 'استفاده شده {used} / مجموع {total}',
'查看订阅': 'مشاهده عضویت ها',
'邮箱': 'ایمیل',
'邮箱验证码': 'کد تایید ایمیل شما',
'发送': 'ارسال',
'重置密码': 'بازنشانی رمز عبور',
'返回登入': 'بازگشت به صفحه ورود',
'邀请码': 'کد دعوت شما',
'复制链接': 'کپی‌کردن لینک',
'完成时间': 'زمان پایان',
'佣金': 'کمیسیون',
'已注册用户数': 'تعداد کاربران ثبت نام شده',
'佣金比例': 'نرخ کمیسیون',
'确认中的佣金': 'کمیسیون تایید شده',
'佣金将会在确认后会到达你的佣金账户。': 'کمیسیون پس از تایید به حساب کمیسیون شما واریز خواهد شد',
'邀请码管理': 'مدیریت کد دعوت',
'生成邀请码': 'یک کد دعوت ایجاد کنید',
'佣金发放记录': 'سابقه پرداخت کمیسیون',
'复制成功': 'آدرس URL با موفقیت کپی شد',
'密码': 'رمز عبور',
'登入': 'ورود',
'注册': 'ثبت‌نام',
'忘记密码': 'رمز عبور فراموش شده',
'# 订单号': '# شماره سفارش',
'周期': 'چرخه',
'订单金额': 'مقدار سفارش',
'订单状态': 'وضعیت سفارش',
'创建时间': 'ساختن',
'操作': 'عملیات',
'查看详情': 'مشاهده جزئیات',
'请选择支付方式': 'لطفا نوع پرداخت را انتخاب کنید',
'请检查信用卡支付信息': 'لطفا اطلاعات پرداخت کارت اعتباری خود را بررسی کنید',
'订单详情': 'اطلاعات سفارش',
'折扣': 'ذخیره',
'折抵': '折抵',
'退款': 'بازگشت هزینه',
'支付方式': 'روش پرداخت',
'填写信用卡支付信息': 'لطفا اطلاعات پرداخت کارت اعتباری خود را بررسی کنید',
'您的信用卡信息只会被用作当次扣款,系统并不会保存,这是我们认为最安全的。': 'اطلاعات کارت اعتباری شما فقط برای بدهی فعلی استفاده می شود، سیستم آن را ذخیره نمی کند، که ما فکر می کنیم امن ترین است.',
'订单总额': 'مجموع سفارش',
'总计': 'مجموع',
'结账': 'پرداخت',
'等待支付中': 'در انتظار پرداخت',
'开通中': 'ایجاید',
'订单系统正在进行处理请稍等1-3分钟。': 'سیستم سفارش در حال پردازش است، لطفا 1-3 دقیقه صبر کنید.',
'已取消': 'صرف نظر شد',
'订单由于超时支付已被取消。': 'سفارش به دلیل پرداخت اضافه کاری لغو شده است',
'已完成': 'به پایان رسید',
'订单已支付并开通。': 'سفارش پرداخت و باز شد.',
'选择订阅': 'انتخاب اشتراک',
'立即订阅': 'همین حالا مشترک شوید',
'配置订阅': 'پیکربندی اشتراک',
'折扣': 'ذخیره',
'付款周期': 'چرخه پرداخت',
'有优惠券?': 'یک کوپن دارید؟',
'验证': 'تأیید',
'订单总额': 'مجموع سفارش',
'下单': 'ایجاد سفارش',
'总计': 'مجموع',
'变更订阅会导致当前订阅被新订阅覆盖,请注意。': 'لطفاً توجه داشته باشید، تغییر یک اشتراک باعث می‌شود که اشتراک فعلی توسط اشتراک جدید بازنویسی شود.',
'该订阅无法续费': 'این اشتراک قابل تمدید نیست',
'选择其他订阅': 'اشتراک دیگری را انتخاب کنید',
'我的钱包': 'کیف پول من',
'账户余额(仅消费)': 'موجودی حساب (فقط خرج کردن)',
'推广佣金(可提现)': 'کمیسیون ارتقاء (قابل برداشت)',
'钱包组成部分': 'اجزای کیف پول',
'划转': 'منتقل کردن',
'推广佣金提现': 'انصراف کمیسیون ارتقاء',
'修改密码': 'تغییر کلمه عبور',
'保存': 'ذخیره کردن',
'旧密码': 'گذرواژه قدیمی',
'新密码': 'رمز عبور جدید',
'请输入旧密码': ', رمز عبور مورد نیاز است',
'请输入新密码': 'گذاشتن گذرواژه',
'通知': 'اعلانات',
'到期邮件提醒': 'یادآوری ایمیل انقضا',
'流量邮件提醒': 'یادآوری ایمیل ترافیک',
'绑定Telegram': 'تلگرام را ببندید',
'立即开始': 'امروز شروع کنید',
'重置订阅信息': 'بازنشانی اطلاعات اشتراک',
'重置': 'تغییر',
'确定要重置订阅信息?': 'آیا مطمئن هستید که می خواهید اطلاعات اشتراک خود را بازنشانی کنید؟',
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更需要重新进行订阅。': 'اگر آدرس یا اطلاعات اشتراک شما لو رفته باشد، این کار را می توان انجام داد. پس از تنظیم مجدد، Uuid و اشتراک شما تغییر خواهد کرد و باید دوباره مشترک شوید.',
'重置成功': 'بازنشانی با موفقیت انجام شد',
'两次新密码输入不同': 'رمز جدید را دو بار وارد کنید',
'两次密码输入不同': 'رمز جدید را دو بار وارد کنید',
'邮箱': 'ایمیل',
'邮箱验证码': 'کد تایید ایمیل شما',
'发送': 'ارسال',
'邀请码': 'کد دعوت شما',
'邀请码(选填)': 'کد دعوت (اختیاری)',
'注册': 'ثبت‌نام',
'返回登入': 'بازگشت به صفحه ورود',
'我已阅读并同意 <a target="_blank" href="{url}">服务条款</a>': 'من <a target="_blank" href="{url}">شرایط خدمات</a> را خوانده‌ام و با آن موافقم',
'请同意服务条款': 'لطفاً با شرایط خدمات موافقت کنید',
'名称': 'نام ویژگی محصول',
'标签': 'برچسب‌ها',
'状态': 'وضعیت',
'节点五分钟内节点在线情况': 'وضعیت آنلاین گره را در عرض پنج دقیقه ثبت کنید',
'倍率': 'بزرگنمایی',
'使用的流量将乘以倍率进行扣除': 'جریان استفاده شده در ضریب برای کسر ضرب خواهد شد',
'更多操作': 'اکشن های بیشتر',
'复制成功': 'آدرس URL با موفقیت کپی شد',
'复制链接': 'کپی‌کردن لینک',
'该订阅长期有效': 'این اشتراک برای مدت طولانی معتبر است',
'已过期': 'منقضی شده',
'已用 {used} / 总计 {total}': 'استفاده شده {used} / مجموع {total}',
'重置订阅信息': 'بازنشانی اطلاعات اشتراک',
'没有可用节点,如果您未订阅或已过期请': 'هیچ گره ای در دسترس نیست، اگر مشترک نیستید یا منقضی شده اید، لطفاً',
'订阅': 'اشتراک',
'确定重置当前已用流量?': 'آیا مطمئن هستید که می خواهید داده های استفاده شده فعلی را بازنشانی کنید؟',
'点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': 'برای رفتن به صندوقدار روی "OK" کلیک کنید. پس از پرداخت سفارش، سیستم اطلاعاتی را که برای ماه استفاده کرده اید پاک می کند.',
'确定': 'تأیید',
'确定要重置订阅信息?': 'آیا مطمئن هستید که می خواهید اطلاعات اشتراک خود را بازنشانی کنید؟',
'如果你的订阅地址或信息泄露可以进行此操作。重置后你的UUID及订阅将会变更需要重新进行订阅。': 'اگر آدرس یا اطلاعات اشتراک شما لو رفته باشد، این کار را می توان انجام داد. پس از تنظیم مجدد، Uuid و اشتراک شما تغییر خواهد کرد و باید دوباره مشترک شوید.',
'重置成功': 'بازنشانی با موفقیت انجام شد',
'低': 'پایین',
'中': 'متوسط',
'高': 'بالا',
'主题': 'موضوع',
'工单级别': 'سطح بلیط',
'工单状态': 'وضعیت درخواست',
'最后回复': 'آخرین پاسخ',
'已关闭': 'پایان‌یافته',
'待回复': 'در انتظار پاسخ',
'已回复': 'پاسخ داده',
'查看': 'بازدیدها',
'关闭': 'بستن',
'新的工单': 'سفارش کار جدید',
'新的工单': 'سفارش کار جدید',
'确认': 'تأیید کردن',
'主题': 'موضوع',
'请输入工单主题': 'لطفا موضوع بلیط را وارد کنید',
'工单等级': 'سطح سفارش کار',
'请选择工单等级': 'لطفا سطح بلیط را انتخاب کنید',
'消息': 'پیام ها',
'请描述你遇到的问题': 'لطفا مشکلی که با آن مواجه شدید را شرح دهید',
'记录时间': 'زمان ضبط',
'实际上行': 'نقطه ضعف واقعی',
'实际下行': 'نقطه ضعف واقعی',
'合计': 'تعداد ارزش‌ها',
'公式:(实际上行 + 实际下行) x 扣费倍率 = 扣除流量': 'فرمول: (خط واقعی + پایین دست واقعی) x نرخ کسر = ترافیک کسر شده',
'复制成功': 'با موفقیت نسخه برداری شد',
'复制订阅地址': 'آدرس اشتراک را کپی کنید',
'导入到': 'واردات در:',
'一键订阅': 'اشتراک با یک کلیک',
'复制订阅': 'اشتراک را کپی کنید',
'推广佣金划转至余额': 'کمیسیون ارتقاء به موجودی منتقل می شود',
'确认': 'تأیید کردن',
'划转后的余额仅用于{title}消费使用': 'موجودی منتقل شده فقط برای مصرف {title} استفاده می شود',
'当前推广佣金余额': 'موجودی کمیسیون ترفیع فعلی',
'划转金额': 'مقدار انتقال',
'请输入需要划转到余额的金额': 'لطفا مبلغی را که باید به موجودی منتقل شود وارد کنید',
'输入内容回复工单...': 'برای پاسخ به تیکت محتوا را وارد کنید...',
'申请提现': 'برای انصراف اقدام کنید',
'确认': 'تاييدات',
'取消': 'انصراف',
'提现方式': 'روش برداشت',
'请选择提现方式': 'لطفاً یک روش برداشت را انتخاب کنید',
'提现账号': 'حساب برداشت',
'请输入提现账号': 'لطفا حساب برداشت را وارد کنید',
'我知道了': 'می فهمم',
'绑定Telegram': 'تلگرام را ببندید',
'第一步': 'گام ۱',
'第二步': 'گام ۲',
'打开Telegram搜索': 'جستجوی تلگرام را باز کنید',
'向机器人发送你的': 'ربات های خود را بفرستید',
'使用文档': 'کار با مستندات',
'最后更新: {date}': 'آخرین به روز رسانی: {date}',
'复制成功': 'کپی با موفقیت انجام شد',
'还有没支付的订单': 'هنوز سفارشات پرداخت نشده وجود دارد',
'立即支付': 'اکنون پرداخت کنید',
'条工单正在处理中': 'بلیط در حال پردازش است',
'立即查看': 'آن را در عمل ببینید',
'使用文档': 'کار با مستندات',
'我的订单': 'درخواست های من',
'流量明细': 'جزئیات\nعبورو مرور در\nمحیط آموزشی',
'配置订阅': 'پیکربندی اشتراک',
'我的邀请': 'دعوتنامه های من',
'节点状态': 'وضعیت گره',
'复制成功': 'آدرس URL با موفقیت کپی شد',
'商品信息': 'مشتریان ثبت نام شده',
'产品名称': 'عنوان کالا',
'类型/周期': 'نوع/چرخه',
'产品流量': 'جریان محصول',
'订单信息': 'اطلاعات سفارش',
'关闭订单': 'سفارش بستن',
'订单号': 'شماره سفارش',
'优惠金额': 'قیمت با تخفیف',
'旧订阅折抵金额': 'مبلغ تخفیف اشتراک قدیمی',
'退款金额': 'کل مبلغ مسترد شده',
'余额支付': 'پرداخت مانده',
'我的工单': 'درخواست های من',
'工单历史': 'تاریخچه بلیط',
'已用流量将在 {reset_day} 日后重置': 'داده‌های استفاده شده ظرف {reset_day} روز بازنشانی می‌شوند',
'已用流量已在今日重置': 'امروز بازنشانی داده استفاده شده است',
'重置已用流量': 'بازنشانی داده های استفاده شده',
'查看节点状态': 'مشاهده وضعیت گره',
'当前已使用流量达{rate}%': 'ترافیک استفاده شده در حال حاضر در {rate}%',
'节点名称': 'نام گره',
'于 {date} 到期,距离到期还有 {day} 天。': 'در {date} منقضی می‌شود که {day} روز دیگر است.',
'Telegram 讨论组': 'گروه گفتگوی تلگرام',
'立即加入': 'حالا پیوستن',
'该订阅无法续费,仅允许新用户购买': 'این اشتراک قابل تمدید نیست، فقط کاربران جدید مجاز به خرید آن هستند',
'重置当月流量': 'بازنشانی ترافیک ماه جاری',
'流量明细仅保留近月数据以供查询。': 'جزئیات ترافیک فقط داده های ماه های اخیر را برای پرس و جو حفظ می کند.',
'扣费倍率': 'نرخ کسر',
'支付手续费': 'پرداخت هزینه های پردازش',
'续费订阅': 'تمدید اشتراک',
'学习如何使用': 'نحوه استفاده را یاد بگیرید',
'快速将节点导入对应客户端进行使用': 'به سرعت گره ها را برای استفاده به مشتری مربوطه وارد کنید',
'对您当前的订阅进行续费': 'با اشتراک فعلی خود خرید کنید',
'对您当前的订阅进行购买': 'با اشتراک فعلی خود خرید کنید',
'捷径': 'میانبر',
'不会使用,查看使用教程': 'استفاده نمی شود، به آموزش مراجعه کنید',
'使用支持扫码的客户端进行订阅': 'برای اشتراک از کلاینتی استفاده کنید که از کد اسکن پشتیبانی می کند',
'扫描二维码订阅': 'برای اشتراک، کد QR را اسکن کنید',
'续费': 'تمدید',
'购买': 'خرید',
'查看教程': 'مشاهده آموزش',
'注意': 'یادداشت!',
'你还有未完成的订单,购买前需要先进行取消,确定取消先前的订单吗?': 'هنوز سفارشات ناتمام دارید. قبل از خرید باید آن را لغو کنید. آیا مطمئن هستید که می‌خواهید سفارش قبلی را لغو کنید؟',
'确定取消': 'تایید لغو',
'返回我的订单': 'بازگشت به سفارش من',
'如果你已经付款,取消订单可能会导致支付失败,确定取消订单吗?': 'اگر قبلاً پرداخت کرده‌اید، لغو سفارش ممکن است باعث عدم موفقیت در پرداخت شود. آیا مطمئن هستید که می‌خواهید سفارش را لغو کنید؟',
'选择最适合你的计划': 'طرحی را انتخاب کنید که مناسب شما باشد',
'全部': 'تمام',
'按周期': 'توسط چرخه',
'遇到问题': 'ما یک مشکل داریم',
'遇到问题可以通过工单与我们沟通': 'در صورت بروز مشکل می توانید از طریق تیکت با ما در ارتباط باشید',
'按流量': 'با جریان',
'搜索文档': 'جستجوی اسناد',
'技术支持': 'دریافت پشتیبانی',
'当前剩余佣金': 'کمیسیون فعلی باقی مانده',
'三级分销比例': 'نسبت توزیع سه لایه',
'累计获得佣金': 'کمیسیون انباشته شده',
'您邀请的用户再次邀请用户将按照订单金额乘以分销等级的比例进行分成。': 'کاربرانی که برای دعوت مجدد از کاربران دعوت می کنید بر اساس نسبت مقدار سفارش ضرب در سطح توزیع تقسیم می شوند.'
};

View File

@ -147,7 +147,7 @@ window.settings.i18n['ja-JP'] = {
'重置订阅信息': 'サブスクリプションURLの変更',
'没有可用节点,如果您未订阅或已过期请': 'ご利用可能なサーバーがありません,プランの期限切れまたは購入なされていない場合は',
'订阅': 'サブスクリプションプラン',
'确定重置当前已用流量?': '确定重置当前已用流量',
'确定重置当前已用流量?': '利用済みデータ量をリセットしますか',
'点击「确定」将会跳转到收银台,支付订单后系统将会清空您当月已使用流量。': '「確定」をクリックし次のページへ移動,お支払い後に当月分のデータ通信量は即時リセットされます',
'确定': '確定',
'确定要重置订阅信息?': 'サブスクリプションURLやUUIDをご変更なされますか',
@ -231,11 +231,11 @@ window.settings.i18n['ja-JP'] = {
'余额支付': '残高ご利用分',
'我的工单': 'お問い合わせ',
'工单历史': 'お問い合わせ履歴',
'已用流量将在 {reset_day} 日后重置': '已用流量将在 {reset_day} 日后重置',
'已用流量已在今日重置': '已用流量已在今日重置',
'重置已用流量': '重置已用流量',
'已用流量将在 {reset_day} 日后重置': '利用済みデータ量は {reset_day} 日後にリセットします',
'已用流量已在今日重置': '利用済みデータ量は本日リセットされました',
'重置已用流量': '利用済みデータ量をリセット',
'查看节点状态': '查看节点状态',
'当前已使用流量达{rate}%': '当前已使用流量达{rate}%',
'当前已使用流量达{rate}%': 'データ使用量が{rate}%になりました',
'节点名称': 'サーバー名',
'于 {date} 到期,距离到期还有 {day} 天。': 'ご利用期限は {date} まで,期限まであと {day} 日',
'Telegram 讨论组': 'Telegramグループ',
@ -246,12 +246,12 @@ window.settings.i18n['ja-JP'] = {
'扣费倍率': '適応レート',
'支付手续费': 'お支払い手数料',
'续费订阅': '購読更新',
'学习如何使用': '学习如何使用',
'快速将节点导入对应客户端进行使用': '快速将节点导入对应客户端进行使用',
'对您当前的订阅进行续费': '对您当前的订阅进行续费',
'对您当前的订阅进行购买': '对您当前的订阅进行购买',
'捷径': '捷径',
'不会使用,查看使用教程': '不会使用,查看使用教程',
'学习如何使用': 'ご利用ガイド',
'快速将节点导入对应客户端进行使用': '最短ルートでサーバー情報をアプリにインポートして使用する',
'对您当前的订阅进行续费': 'ご利用中のサブスクの継続料金を支払う',
'对您当前的订阅进行购买': 'ご利用中のサブスクを再度購入する',
'捷径': 'ショートカット',
'不会使用,查看使用教程': 'ご利用方法がわからない方はナレッジベースをご閲覧ください',
'使用支持扫码的客户端进行订阅': '使用支持扫码的客户端进行订阅',
'扫描二维码订阅': '扫描二维码订阅',
'续费': '更新',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -38,7 +38,8 @@
'ja-JP',
'vi-VN',
'ko-KR',
'zh-TW'
'zh-TW',
'fa-IR'
],
logo: '{{$logo}}'
}
@ -49,6 +50,7 @@
<script src="/theme/{{$theme}}/assets/i18n/ja-JP.js?v={{$version}}"></script>
<script src="/theme/{{$theme}}/assets/i18n/vi-VN.js?v={{$version}}"></script>
<script src="/theme/{{$theme}}/assets/i18n/ko-KR.js?v={{$version}}"></script>
<script src="/theme/{{$theme}}/assets/i18n/fa-IR.js?v={{$version}}"></script>
</head>
<body>

View File

@ -88,10 +88,11 @@
"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",
"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",
"Register frequently, please try again after :minute minute": "Register frequently, please try again after :minute minute",
"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",
"Login to :name": "Login to :name",
"Sending frequently, please try again later": "Sending frequently, please try again later",
"Current product is sold out": "Current product is sold out"
"Current product is sold out": "Current product is sold out",
"There are too many password errors, please try again after 30 minutes.": "There are too many password errors, please try again after 30 minutes."
}

View File

@ -88,10 +88,11 @@
"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": "此优惠券无法用于该付款周期",
"Request failed, please try again later": "请求失败,请稍后再试",
"Register frequently, please try again after 1 hour": "注册频繁请等待1小时后再次尝试",
"Register frequently, please try again after :minute minute": "注册频繁,请等待 :minute 分钟后再次尝试",
"Uh-oh, we've had some problems, we're working on it.": "遇到了些问题,我们正在进行处理",
"This subscription reset package does not apply to your subscription": "该订阅重置包不适用于你的订阅",
"Login to :name": "登入到 :name",
"Sending frequently, please try again later": "发送频繁,请稍后再试",
"Current product is sold out": "当前商品已售罄"
"Current product is sold out": "当前商品已售罄",
"There are too many password errors, please try again after 30 minutes.": "密码错误次数过多,请 30 分钟后再试"
}

View File

@ -20,7 +20,8 @@
},
version: '{{$version}}',
background_url: '{{$background_url}}',
logo: '{{$logo}}'
logo: '{{$logo}}',
secure_path: '{{$secure_path}}'
}
</script>
</head>

View File

@ -38,7 +38,8 @@ Route::get('/', function (Request $request) {
return view('theme::' . config('v2board.frontend_theme', 'v2board') . '.dashboard', $renderParams);
});
Route::get('/' . config('v2board.frontend_admin_path', 'admin'), function () {
//TODO:: 兼容
Route::get('/' . config('v2board.secure_path', config('v2board.frontend_admin_path', crc32(config('app.key')))), function () {
return view('admin', [
'title' => config('v2board.app_name', 'V2Board'),
'theme_sidebar' => config('v2board.frontend_theme_sidebar', 'light'),
@ -46,6 +47,7 @@ Route::get('/' . config('v2board.frontend_admin_path', 'admin'), function () {
'theme_color' => config('v2board.frontend_theme_color', 'default'),
'background_url' => config('v2board.frontend_background_url'),
'version' => config('app.version'),
'logo' => config('v2board.logo')
'logo' => config('v2board.logo'),
'secure_path' => config('v2board.secure_path', config('v2board.frontend_admin_path', crc32(config('app.key'))))
]);
});