refactor: 重构规范部分代码、邮件队列增加失败重试、去除多个支付方式、更新依赖

This commit is contained in:
xboard 2024-04-10 00:51:03 +08:00
parent ec63e05575
commit 4c6c7182e2
50 changed files with 421 additions and 1005 deletions

View File

@ -46,8 +46,7 @@ class CheckServer extends Command
private function checkOffline()
{
$serverService = new ServerService();
$servers = $serverService->getAllServers();
$servers = ServerService::getAllServers();
foreach ($servers as $server) {
if ($server['parent_id']) continue;
if ($server['last_check_at'] && (time() - $server['last_check_at']) > 1800) {

View File

@ -45,7 +45,7 @@ class CheckTicket extends Command
->get();
foreach ($tickets as $ticket) {
if ($ticket->user_id === $ticket->last_reply_user_id) continue;
$ticket->status = 1;
$ticket->status = Ticket::STATUS_CLOSED;
$ticket->save();
}
}

View File

@ -5,8 +5,6 @@ namespace App\Console\Commands;
use App\Services\MailService;
use Illuminate\Console\Command;
use App\Models\User;
use App\Models\MailLog;
use App\Jobs\SendEmailJob;
class SendRemindMail extends Command
{

View File

@ -52,7 +52,8 @@ class XboardInstall extends Command
$this->info(" \ \/ / | __ \ / _ \ / _` | '__/ _` | ");
$this->info(" / /\ \ | |_) | (_) | (_| | | | (_| | ");
$this->info("/_/ \_\|____/ \___/ \__,_|_| \__,_| ");
if ((\File::exists(base_path() . '/.env') && $this->getEnvValue('INSTALLED'))
if (
(\File::exists(base_path() . '/.env') && $this->getEnvValue('INSTALLED'))
|| (env('INSTALLED', false) && $isDocker)
) {
$securePath = admin_setting('secure_path', admin_setting('frontend_admin_path', hash('crc32b', config('app.key'))));
@ -69,14 +70,6 @@ class XboardInstall extends Command
// 选择是否使用Sqlite
if (confirm(label: '是否启用Sqlite(无需额外安装)代替Mysql', default: false, yes: '启用', no: '不启用')) {
$sqliteFile = '.docker/.data/database.sqlite';
if (!file_exists(base_path($sqliteFile))) {
// 创建空文件
if (!touch(base_path($sqliteFile))) {
echo "sqlite创建成功: $sqliteFile";
} else {
echo "sqlite创建失败";
}
}
$envConfig = [
'DB_CONNECTION' => 'sqlite',
'DB_DATABASE' => $sqliteFile,
@ -147,12 +140,16 @@ class XboardInstall extends Command
if (!copy(base_path() . '/.env.example', base_path() . '/.env')) {
abort(500, '复制环境文件失败,请检查目录权限');
};
$email = text(label: '请输入管理员账号',required: true,
}
;
$email = text(
label: '请输入管理员账号',
required: true,
validate: fn(string $email): ?string => match (true) {
!filter_var($email, FILTER_VALIDATE_EMAIL) => '请输入有效的邮箱地址.',
default => null,
});
}
);
$password = Helper::guid(false);
$this->saveToEnv($envConfig);
@ -197,8 +194,8 @@ class XboardInstall extends Command
private function saveToEnv($data = [])
{
function set_env_var($key, $value)
{
foreach ($data as $key => $value) {
function ($key, $value) {
if (!is_bool(strpos($value, ' '))) {
$value = '"' . $value . '"';
}
@ -220,9 +217,7 @@ class XboardInstall extends Command
$file = fopen($envPath, 'w');
fwrite($file, $contents);
return fclose($file);
}
foreach($data as $key => $value) {
set_env_var($key, $value);
};
}
return true;
}

View File

@ -5,10 +5,8 @@ namespace App\Exceptions;
use App\Helpers\ApiResponse;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Support\Arr;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Illuminate\View\ViewException;
use Throwable;
use Facade\Ignition\Exceptions\ViewException;
use Illuminate\Support\Facades\Log;
class Handler extends ExceptionHandler
{

View File

@ -1,4 +1,5 @@
<?php
use App\Support\Setting;
if (! function_exists("get_request_content")){
@ -20,14 +21,14 @@ if (! function_exists('admin_setting')) {
function admin_setting($key = null, $default = null)
{
if ($key === null) {
return app('setting');
return App::make(Setting::class)->toArray();
}
if (is_array($key)) {
app('setting')->save($key);
return;
App::make(Setting::class)->save($key);
return '';
}
$default = config('v2board.'. $key) ?? $default;
return app('setting')->get($key) ?? $default ;
return App::make(Setting::class)->get($key) ?? $default ;
}
}

View File

@ -4,8 +4,8 @@ namespace App\Http\Controllers\V1\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\ConfigSave;
use App\Jobs\SendEmailJob;
use App\Models\Setting;
use App\Services\MailService;
use App\Services\TelegramService;
use App\Utils\Dict;
use Illuminate\Http\Request;
@ -33,7 +33,7 @@ class ConfigController extends Controller
public function testSendMail(Request $request)
{
$obj = new SendEmailJob([
$mailLog = MailService::sendEmail([
'email' => $request->user['email'],
'subject' => 'This is xboard test email',
'template_name' => 'notify',
@ -45,7 +45,7 @@ class ConfigController extends Controller
]);
return response([
'data' => true,
'log' => $obj->handle()
'log' => $mailLog
]);
}

View File

@ -160,13 +160,13 @@ class OrderController extends Controller
$order->total_amount = $request->input('total_amount');
if ($order->period === 'reset_price') {
$order->type = 4;
$order->type = Order::TYPE_RESET_TRAFFIC;
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id) {
$order->type = 3;
$order->type = Order::TYPE_UPGRADE;
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) {
$order->type = 2;
$order->type = Order::TYPE_RENEWAL;
} else {
$order->type = 1;
$order->type = Order::TYPE_NEW_PURCHASE;
}
$orderService->setInvite($user);

View File

@ -19,8 +19,7 @@ class GroupController extends Controller
return $this->success([ServerGroup::find($request->input('group_id'))]);
}
$serverGroups = ServerGroup::get();
$serverService = new ServerService();
$servers = $serverService->getAllServers();
$servers = ServerService::getAllServers();
foreach ($serverGroups as $k => $v) {
$serverGroups[$k]['user_count'] = User::where('group_id', $v['id'])->count();
$serverGroups[$k]['server_count'] = 0;

View File

@ -12,8 +12,7 @@ class ManageController extends Controller
{
public function getNodes(Request $request)
{
$serverService = new ServerService();
return $this->success($serverService->getAllServers());
return $this->success(ServerService::getAllServers());
}
public function sort(Request $request)

View File

@ -2,12 +2,10 @@
namespace App\Http\Controllers\V1\Admin\Server;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\ServerTrojanSave;
use App\Http\Requests\Admin\ServerTrojanUpdate;
use App\Models\ServerTrojan;
use App\Services\ServerService;
use Illuminate\Http\Request;
class TrojanController extends Controller
@ -75,10 +73,4 @@ class TrojanController extends Controller
ServerTrojan::create($server->toArray());
return $this->success(true);
}
public function viewConfig(Request $request)
{
$serverService = new ServerService();
$config = $serverService->getTrojanConfig($request->input('node_id'), 23333);
return $this->success($config);
}
}

View File

@ -80,7 +80,7 @@ class TicketController extends Controller
]);
try {
$ticket = Ticket::findOrFail($request->input('id'));
$ticket->status = 1;
$ticket->status = Ticket::STATUS_CLOSED;
$ticket->save();
return $this->success(true);
} catch (ModelNotFoundException $e) {

View File

@ -17,8 +17,7 @@ class AppController extends Controller
$user = $request->user;
$userService = new UserService();
if ($userService->isAvailable($user)) {
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
$servers = ServerService::getAvailableServers($user);
}
$defaultConfig = base_path() . '/resources/rules/app.clash.yaml';
$customConfig = base_path() . '/resources/rules/custom.app.clash.yaml';

View File

@ -65,8 +65,7 @@ class ClientController extends Controller
$region = $geo['region'] ?? null;
// 获取服务器列表
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
$servers = ServerService::getAvailableServers($user);
// 判断不满足,不满足的直接过滤掉
$serversFiltered = collect($servers)->reject(function ($server) use ($typesArr, $filterArr, $region, $supportHy2){

View File

@ -17,7 +17,8 @@ class PaymentController extends Controller
try {
$paymentService = new PaymentService($method, null, $uuid);
$verify = $paymentService->notify($request->input());
if (!$verify) return $this->fail([422,'verify error']);
if (!$verify)
return $this->fail([422, 'verify error']);
if (!$this->handle($verify['trade_no'], $verify['callback_no'])) {
return $this->fail([400, 'handle error']);
}
@ -34,7 +35,8 @@ class PaymentController extends Controller
if (!$order) {
return $this->fail([400202, 'order is not found']);
}
if ($order->status !== 0) return true;
if ($order->status !== Order::STATUS_PENDING)
return true;
$orderService = new OrderService($order);
if (!$orderService->paid($callbackNo)) {
return false;

View File

@ -13,24 +13,23 @@ class TelegramController extends Controller
protected $commands = [];
protected $telegramService;
public function __construct(Request $request)
public function __construct(TelegramService $telegramService)
{
if ($request->input('access_token') !== md5(admin_setting('telegram_bot_token'))) {
throw new ApiException('access_token is error', 401);
}
$this->telegramService = new TelegramService();
$this->telegramService = $telegramService;
}
public function webhook(Request $request)
{
if ($request->input('access_token') !== md5(admin_setting('telegram_bot_token'))) {
throw new ApiException('access_token is error', 401);
}
$data = json_decode(get_request_content(),true);
$this->formatMessage($data);
$this->formatChatJoinRequest($data);
$this->handle();
}
public function handle()
private function handle()
{
if (!$this->msg) return;
$msg = $this->msg;
@ -68,7 +67,7 @@ class TelegramController extends Controller
}
}
public function getBotName()
private function getBotName()
{
$response = $this->telegramService->getMe();
return $response->result->username;

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers\V1\Passport;
use App\Exceptions\ApiException;
use App\Helpers\ResponseEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Passport\AuthForget;

View File

@ -18,16 +18,6 @@ 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');
if (empty($token)) {
throw new ApiException('token is null');
}
if ($token !== admin_setting('server_token')) {
throw new ApiException('token is error');
}
}
// 后端获取用户
public function user(Request $request)
@ -39,8 +29,7 @@ class DeepbworkController extends Controller
return $this->fail([400,'节点不存在']);
}
Cache::put(CacheKey::get('SERVER_VMESS_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers($server->group_id);
$users = ServerService::getAvailableUsers($server->group_id);
$result = [];
foreach ($users as $user) {
$user->v2ray_user = [

View File

@ -17,17 +17,6 @@ use Illuminate\Support\Facades\Cache;
*/
class ShadowsocksTidalabController extends Controller
{
public function __construct(Request $request)
{
$token = $request->input('token');
if (empty($token)) {
throw new ApiException('token is null');
}
if ($token !== admin_setting('server_token')) {
throw new ApiException('token is error');
}
}
// 后端获取用户
public function user(Request $request)
{
@ -38,8 +27,7 @@ class ShadowsocksTidalabController extends Controller
return $this->fail([400,'节点不存在']);
}
Cache::put(CacheKey::get('SERVER_SHADOWSOCKS_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers($server->group_id);
$users = ServerService::getAvailableUsers($server->group_id);
$result = [];
foreach ($users as $user) {
array_push($result, [

View File

@ -17,17 +17,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');
if (empty($token)) {
throw new ApiException('token is null');
}
if ($token !== admin_setting('server_token')) {
throw new ApiException('token is error');
}
}
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 user(Request $request)
@ -39,8 +29,7 @@ class TrojanTidalabController extends Controller
return $this->fail([400, '节点不存在']);
}
Cache::put(CacheKey::get('SERVER_TROJAN_LAST_CHECK_AT', $server->id), time(), 3600);
$serverService = new ServerService();
$users = $serverService->getAvailableUsers($server->group_id);
$users = ServerService::getAvailableUsers($server->group_id);
$result = [];
foreach ($users as $user) {
$user->trojan_user = [

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers\V1\Server;
use App\Exceptions\ApiException;
use App\Http\Controllers\Controller;
use App\Services\ServerService;
use App\Services\UserService;
@ -10,40 +9,23 @@ use App\Utils\CacheKey;
use App\Utils\Helper;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class UniProxyController extends Controller
{
private $nodeType;
private $nodeInfo;
private $nodeId;
private $serverService;
public function __construct(ServerService $serverService, Request $request)
{
$this->serverService = $serverService;
$this->nodeId = $request->input('node_id');
$this->nodeType = $request->input('node_type');
$this->nodeInfo = $this->serverService->getServer($this->nodeId, $this->nodeType);
if(!$this->nodeInfo) {
throw new ApiException('server is not exist', 500);
};
}
// 后端获取用户
public function user(Request $request)
{
ini_set('memory_limit', -1);
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_CHECK_AT', $this->nodeInfo->id), time(), 3600);
$users = $this->serverService->getAvailableUsers($this->nodeInfo->group_id);
$users = $users->toArray();
Cache::put(CacheKey::get('SERVER_' . strtoupper($request->input('node_type')) . '_LAST_CHECK_AT', $request->input('node_id')), time(), 3600);
$users = ServerService::getAvailableUsers($request->input('node_info')->group_id)->toArray();
$response['users'] = $users;
$eTag = sha1(json_encode($response));
if (strpos($request->header('If-None-Match'), $eTag) !== false) {
return response(null, 304);
};
}
return response($response)->header('ETag', "\"{$eTag}\"");
}
@ -51,14 +33,18 @@ class UniProxyController extends Controller
// 后端提交数据
public function push(Request $request)
{
$data = get_request_content();
$data = json_decode($data, true);
$data = $request->validate([
"*.0" => 'integer',
"*.1" => 'integer'
]);
$nodeType = $request->input('node_type');
$nodeId = $request->input('node_id');
// 增加单节点多服务器统计在线人数
$ip = $request->ip();
$id = $request->input("id");
$time = time();
$cacheKey = CacheKey::get('MULTI_SERVER_' . strtoupper($this->nodeType) . '_ONLINE_USER', $this->nodeInfo->id);
$cacheKey = CacheKey::get('MULTI_SERVER_' . strtoupper($nodeType) . '_ONLINE_USER', $nodeId);
// 1、获取节点节点在线人数缓存
$onlineUsers = Cache::get($cacheKey) ?? [];
@ -87,70 +73,71 @@ class UniProxyController extends Controller
Cache::put($cacheKey, $onlineUsers, 3600);
$online_user = $onlineCollection->sum('online_user');
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_ONLINE_USER', $this->nodeInfo->id), $online_user, 3600);
Cache::put(CacheKey::get('SERVER_' . strtoupper($this->nodeType) . '_LAST_PUSH_AT', $this->nodeInfo->id), time(), 3600);
Cache::put(CacheKey::get('SERVER_' . strtoupper($nodeType) . '_ONLINE_USER', $nodeId), $online_user, 3600);
Cache::put(CacheKey::get('SERVER_' . strtoupper($nodeType) . '_LAST_PUSH_AT', $nodeId), time(), 3600);
$userService = new UserService();
$userService->trafficFetch($this->nodeInfo->toArray(), $this->nodeType, $data , $ip);
$userService->trafficFetch($request->input('node_info')->toArray(), $nodeType, $data, $ip);
return $this->success(true);
}
// 后端获取配置
public function config(Request $request)
{
switch ($this->nodeType) {
$nodeType = $request->input('node_type');
$nodeInfo = $request->input('node_info');
switch ($nodeType) {
case 'shadowsocks':
$response = [
'server_port' => $this->nodeInfo->server_port,
'cipher' => $this->nodeInfo->cipher,
'obfs' => $this->nodeInfo->obfs,
'obfs_settings' => $this->nodeInfo->obfs_settings
'server_port' => $nodeInfo->server_port,
'cipher' => $nodeInfo->cipher,
'obfs' => $nodeInfo->obfs,
'obfs_settings' => $nodeInfo->obfs_settings
];
if ($this->nodeInfo->cipher === '2022-blake3-aes-128-gcm') {
$response['server_key'] = Helper::getServerKey($this->nodeInfo->created_at, 16);
if ($nodeInfo->cipher === '2022-blake3-aes-128-gcm') {
$response['server_key'] = Helper::getServerKey($nodeInfo->created_at, 16);
}
if ($this->nodeInfo->cipher === '2022-blake3-aes-256-gcm') {
$response['server_key'] = Helper::getServerKey($this->nodeInfo->created_at, 32);
if ($nodeInfo->cipher === '2022-blake3-aes-256-gcm') {
$response['server_key'] = Helper::getServerKey($nodeInfo->created_at, 32);
}
break;
case 'vmess':
$response = [
'server_port' => $this->nodeInfo->server_port,
'network' => $this->nodeInfo->network,
'networkSettings' => $this->nodeInfo->networkSettings,
'tls' => $this->nodeInfo->tls
'server_port' => $nodeInfo->server_port,
'network' => $nodeInfo->network,
'networkSettings' => $nodeInfo->networkSettings,
'tls' => $nodeInfo->tls
];
break;
case 'trojan':
$response = [
'host' => $this->nodeInfo->host,
'server_port' => $this->nodeInfo->server_port,
'server_name' => $this->nodeInfo->server_name,
'network' => $this->nodeInfo->network,
'networkSettings' => $this->nodeInfo->networkSettings,
'host' => $nodeInfo->host,
'server_port' => $nodeInfo->server_port,
'server_name' => $nodeInfo->server_name,
'network' => $nodeInfo->network,
'networkSettings' => $nodeInfo->networkSettings,
];
break;
case 'hysteria':
$response = [
'version' => $this->nodeInfo->version,
'host' => $this->nodeInfo->host,
'server_port' => $this->nodeInfo->server_port,
'server_name' => $this->nodeInfo->server_name,
'up_mbps' => $this->nodeInfo->up_mbps,
'down_mbps' => $this->nodeInfo->down_mbps,
'obfs' => $this->nodeInfo->is_obfs ? Helper::getServerKey($this->nodeInfo->created_at, 16) : null
'version' => $nodeInfo->version,
'host' => $nodeInfo->host,
'server_port' => $nodeInfo->server_port,
'server_name' => $nodeInfo->server_name,
'up_mbps' => $nodeInfo->up_mbps,
'down_mbps' => $nodeInfo->down_mbps,
'obfs' => $nodeInfo->is_obfs ? Helper::getServerKey($nodeInfo->created_at, 16) : null
];
break;
case "vless":
$response = [
'server_port' => $this->nodeInfo->server_port,
'network' => $this->nodeInfo->network,
'network_settings' => $this->nodeInfo->network_settings,
'networkSettings' => $this->nodeInfo->network_settings,
'tls' => $this->nodeInfo->tls,
'flow' => $this->nodeInfo->flow,
'tls_settings' => $this->nodeInfo->tls_settings
'server_port' => $nodeInfo->server_port,
'network' => $nodeInfo->network,
'network_settings' => $nodeInfo->network_settings,
'networkSettings' => $nodeInfo->network_settings,
'tls' => $nodeInfo->tls,
'flow' => $nodeInfo->flow,
'tls_settings' => $nodeInfo->tls_settings
];
break;
}
@ -158,8 +145,8 @@ class UniProxyController extends Controller
'push_interval' => (int) admin_setting('server_push_interval', 60),
'pull_interval' => (int) admin_setting('server_pull_interval', 60)
];
if ($this->nodeInfo['route_id']) {
$response['routes'] = $this->serverService->getRoutes($this->nodeInfo['route_id']);
if ($nodeInfo['route_id']) {
$response['routes'] = ServerService::getRoutes($nodeInfo['route_id']);
}
$eTag = sha1(json_encode($response));
if (strpos($request->header('If-None-Match'), $eTag) !== false) {

View File

@ -73,7 +73,7 @@ class TicketController extends Controller
if (!$ticket) {
return $this->fail([400202,'工单不存在']);
}
$ticket->status = 1;
$ticket->status = Ticket::STATUS_CLOSED;
if (!$ticket->save()) {
return $this->fail([500, '工单关闭失败']);
}

View File

@ -8,8 +8,6 @@ use App\Models\User;
use App\Services\ServerService;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class ServerController extends Controller
{
@ -19,8 +17,7 @@ class ServerController extends Controller
$servers = [];
$userService = new UserService();
if ($userService->isAvailable($user)) {
$serverService = new ServerService();
$servers = $serverService->getAvailableServers($user);
$servers = ServerService::getAvailableServers($user);
}
$eTag = sha1(json_encode(array_column($servers, 'cache_key')));
if (strpos($request->header('If-None-Match'), $eTag) !== false ) {

View File

@ -118,7 +118,7 @@ class TicketController extends Controller
if (!$ticket) {
return $this->fail([400, __('Ticket does not exist')]);
}
$ticket->status = 1;
$ticket->status = Ticket::STATUS_CLOSED;
if (!$ticket->save()) {
return $this->fail([500, __('Close failed')]);
}

View File

@ -56,7 +56,6 @@ class Kernel extends HttpKernel
* @var array
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
@ -83,7 +82,6 @@ class Kernel extends HttpKernel
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,

View File

@ -1,21 +0,0 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function redirectTo($request)
{
if (!$request->expectsJson()) {
return route('login');
}
}
}

View File

@ -2,6 +2,8 @@
namespace App\Http\Middleware;
use App\Exceptions\ApiException;
use App\Services\ServerService;
use Closure;
use Illuminate\Http\Request;
@ -22,27 +24,22 @@ class Server
'hysteria2' => 'hysteria'
];
$request->validate([
'token' => ['required','string',function ($attribute, $value, $fail) {
if ($value != admin_setting('server_token')) {
$fail("The $attribute is error.");
}
}],
'token' => ['required', 'string', 'in:' . admin_setting('server_token')],
'node_id' => 'required',
'node_type' => [
'nullable',
'string',
'regex:/^(?i)(hysteria|hysteria2|vless|trojan|vmess|v2ray|tuic|shadowsocks|shadowsocks-plugin)$/',
function ($attribute, $value, $fail)use($aliasTypes) {
// 将值转换为小写
request()->merge([$attribute => strtolower($value)]);
// 类别别名
if (in_array($value, array_keys($aliasTypes))){
request()->merge([$attribute => $aliasTypes[$value]]);
}
function ($attribute, $value, $fail) use ($aliasTypes, $request) {
$request->merge([$attribute => strtolower(isset ($aliasTypes[$value]) ? $aliasTypes[$value] : $value)]);
},
]
], [
'token.in' => 'Token is error!',
'node_type.regex' => 'node_type is error!'
]);
$nodeInfo = ServerService::getServer($request->input('node_id'), $request->input('node_type'));
if (!$nodeInfo)
throw new ApiException('server is not exist!');
$request->merge(['node_info' => $nodeInfo]);
return $next($request);
}
}

View File

@ -1,7 +1,10 @@
<?php
namespace App\Http\Routes\V1;
use App\Exceptions\ApiException;
use App\Http\Controllers\V1\Server\DeepbworkController;
use App\Http\Controllers\V1\Server\ShadowsocksTidalabController;
use App\Http\Controllers\V1\Server\TrojanTidalabController;
use App\Http\Controllers\V1\Server\UniProxyController;
use Illuminate\Contracts\Routing\Registrar;
class ServerRoute
@ -12,13 +15,25 @@ class ServerRoute
'prefix' => 'server',
'middleware' => 'server'
], function ($router) {
$router->any('/{class}/{action}', function($class, $action) {
$controllerClass = "\\App\\Http\\Controllers\\V1\\Server\\" . ucfirst($class) . "Controller";
if(!(class_exists($controllerClass) && method_exists($controllerClass, $action))){
throw new ApiException('Not Found',404);
};
$ctrl = \App::make($controllerClass);
return \App::call([$ctrl, $action]);
$router->prefix('UniProxy')->group(function ($route) {
$route->get('config', [UniProxyController::class, 'config']);
$route->get('user', [UniProxyController::class, 'user']);
$route->post('push', [UniProxyController::class, 'push']);
$route->post('alive', [UniProxyController::class, 'alive']);
});
$router->prefix('Deepbwork')->group(function ($route) {
$route->get('config', [DeepbworkController::class, 'config']);
$route->get('user', [DeepbworkController::class, 'user']);
$route->post('submit', [DeepbworkController::class, 'submit']);
});
$router->prefix('ShadowsocksTidalab')->group(function ($route) {
$route->get('user', [ShadowsocksTidalabController::class, 'user']);
$route->post('submit', [ShadowsocksTidalabController::class, 'submit']);
});
$router->prefix('TrojanTidalab')->group(function ($route) {
$route->get('config', [TrojanTidalabController::class, 'config']);
$route->get('user', [TrojanTidalabController::class, 'user']);
$route->post('submit', [TrojanTidalabController::class, 'submit']);
});
});
}

View File

@ -2,14 +2,12 @@
namespace App\Jobs;
use App\Services\MailService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
use App\Models\MailLog;
class SendEmailJob implements ShouldQueue
{
@ -36,40 +34,9 @@ class SendEmailJob implements ShouldQueue
*/
public function handle()
{
if (admin_setting('email_host')) {
Config::set('mail.host', admin_setting('email_host', config('mail.host')));
Config::set('mail.port', admin_setting('email_port', config('mail.port')));
Config::set('mail.encryption', admin_setting('email_encryption', config('mail.encryption')));
Config::set('mail.username', admin_setting('email_username', config('mail.username')));
Config::set('mail.password', admin_setting('email_password', config('mail.password')));
Config::set('mail.from.address', admin_setting('email_from_address', config('mail.from.address')));
Config::set('mail.from.name', admin_setting('app_name', 'XBoard'));
}
$params = $this->params;
$email = $params['email'];
$subject = $params['subject'];
$params['template_name'] = 'mail.' . admin_setting('email_template', 'default') . '.' . $params['template_name'];
try {
Mail::send(
$params['template_name'],
$params['template_value'],
function ($message) use ($email, $subject) {
$message->to($email)->subject($subject);
}
);
} catch (\Exception $e) {
$error = $e->getMessage();
}
$log = [
'email' => $params['email'],
'subject' => $params['subject'],
'template_name' => $params['template_name'],
'error' => isset($error) ? $error : NULL
];
MailLog::create($log);
$log['config'] = config('mail');
return $log;
$mailLog = MailService::sendEmail($this->params);
if($mailLog['error']){
$this->release(); //发送失败将触发重试
}
}
}

View File

@ -2,9 +2,6 @@
namespace App\Jobs;
use App\Models\User;
use App\Services\MailService;
use App\Services\ServerService;
use App\Services\StatisticalService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;

View File

@ -14,4 +14,30 @@ class Order extends Model
'updated_at' => 'timestamp',
'surplus_order_ids' => 'array'
];
const STATUS_PENDING = 0; // 待支付
const STATUS_PROCESSING = 1; // 开通中
const STATUS_CANCELLED = 2; // 已取消
const STATUS_COMPLETED = 3; // 已完成
const STATUS_DISCOUNTED = 4; // 已折抵
public static $statusMap = [
self::STATUS_PENDING => '待支付',
self::STATUS_PROCESSING => '开通中',
self::STATUS_CANCELLED => '已取消',
self::STATUS_COMPLETED => '已完成',
self::STATUS_DISCOUNTED => '已折抵',
];
const TYPE_NEW_PURCHASE = 1; // 新购
const TYPE_RENEWAL = 2; // 续费
const TYPE_UPGRADE = 3; // 升级
const TYPE_RESET_TRAFFIC = 4; //流量重置包
public static $typeMap = [
self::TYPE_NEW_PURCHASE => '新购',
self::TYPE_RENEWAL => '续费',
self::TYPE_UPGRADE => '升级',
self::TYPE_RESET_TRAFFIC => '流量重置',
];
}

View File

@ -14,6 +14,12 @@ class Ticket extends Model
'updated_at' => 'timestamp'
];
const STATUS_OPENING = 0;
const STATUS_CLOSED = 1;
public static $statusMap = [
self::STATUS_OPENING => '开启',
self::STATUS_CLOSED => '关闭'
];
public function message()
{

View File

@ -7,6 +7,7 @@ namespace App\Payments;
use App\Exceptions\ApiException;
class AlipayF2F {
protected $config;
public function __construct($config)
{
$this->config = $config;

View File

@ -1,11 +1,15 @@
<?php
namespace App\Payments;
use App\Exceptions\ApiException;
class BTCPay {
public function __construct($config) {
class BTCPay
{
protected $config;
public function __construct($config)
{
$this->config = $config;
}
@ -35,7 +39,8 @@ class BTCPay {
];
}
public function pay($order) {
public function pay($order)
{
$params = [
'jsonResponse' => true,
@ -61,7 +66,8 @@ class BTCPay {
];
}
public function notify($params) {
public function notify($params)
{
$payload = trim(get_request_content());
$headers = getallheaders();
@ -98,11 +104,11 @@ class BTCPay {
'trade_no' => $out_trade_no,
'callback_no' => $pay_trade_no
];
}
private function _curlPost($url,$params=false){
private function _curlPost($url, $params = false)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
@ -111,7 +117,9 @@ class BTCPay {
curl_setopt($ch, CURLOPT_TIMEOUT, 300);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
curl_setopt(
$ch, CURLOPT_HTTPHEADER, array('Authorization:' .'token '.$this->config['btcpay_api_key'], 'Content-Type: application/json')
$ch,
CURLOPT_HTTPHEADER,
array('Authorization:' . 'token ' . $this->config['btcpay_api_key'], 'Content-Type: application/json')
);
$result = curl_exec($ch);
curl_close($ch);
@ -143,6 +151,4 @@ class BTCPay {
return !$ret;
}
}
}

View File

@ -1,10 +1,14 @@
<?php
namespace App\Payments;
use App\Exceptions\ApiException;
class CoinPayments {
public function __construct($config) {
class CoinPayments
{
protected $config;
public function __construct($config)
{
$this->config = $config;
}

View File

@ -1,10 +1,14 @@
<?php
namespace App\Payments;
use App\Exceptions\ApiException;
class Coinbase {
public function __construct($config) {
class Coinbase
{
protected $config;
public function __construct($config)
{
$this->config = $config;
}
@ -29,7 +33,8 @@ class Coinbase {
];
}
public function pay($order) {
public function pay($order)
{
$params = [
'name' => '订阅套餐',
@ -59,7 +64,8 @@ class Coinbase {
];
}
public function notify($params) {
public function notify($params)
{
$payload = trim(get_request_content());
$json_param = json_decode($payload, true);
@ -80,11 +86,11 @@ class Coinbase {
'trade_no' => $out_trade_no,
'callback_no' => $pay_trade_no
];
}
private function _curlPost($url,$params=false){
private function _curlPost($url, $params = false)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
@ -93,7 +99,9 @@ class Coinbase {
curl_setopt($ch, CURLOPT_TIMEOUT, 300);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
curl_setopt(
$ch, CURLOPT_HTTPHEADER, array('X-CC-Api-Key:' .$this->config['coinbase_api_key'], 'X-CC-Version: 2018-03-22')
$ch,
CURLOPT_HTTPHEADER,
array('X-CC-Api-Key:' . $this->config['coinbase_api_key'], 'X-CC-Version: 2018-03-22')
);
$result = curl_exec($ch);
curl_close($ch);
@ -124,6 +132,4 @@ class Coinbase {
return !$ret;
}
}
}

View File

@ -2,7 +2,9 @@
namespace App\Payments;
class EPay {
class EPay
{
protected $config;
public function __construct($config)
{
$this->config = $config;

View File

@ -1,118 +0,0 @@
<?php
/**
* 自己写别抄抄NMB抄
*/
namespace App\Payments;
use App\Exceptions\ApiException;
use Stripe\Source;
use Stripe\Stripe;
class StripeAlipay {
public function __construct($config)
{
$this->config = $config;
}
public function form()
{
return [
'currency' => [
'label' => '货币单位',
'description' => '',
'type' => 'input',
],
'stripe_sk_live' => [
'label' => 'SK_LIVE',
'description' => '',
'type' => 'input',
],
'stripe_webhook_key' => [
'label' => 'WebHook密钥签名',
'description' => '',
'type' => 'input',
]
];
}
public function pay($order)
{
$currency = $this->config['currency'];
$exchange = $this->exchange('CNY', strtoupper($currency));
if (!$exchange) {
throw new ApiException(__('Currency conversion has timed out, please try again later'));
}
Stripe::setApiKey($this->config['stripe_sk_live']);
$source = Source::create([
'amount' => floor($order['total_amount'] * $exchange),
'currency' => $currency,
'type' => 'alipay',
'statement_descriptor' => $order['trade_no'],
'metadata' => [
'user_id' => $order['user_id'],
'out_trade_no' => $order['trade_no'],
'identifier' => ''
],
'redirect' => [
'return_url' => $order['return_url']
]
]);
if (!$source['redirect']['url']) {
throw new ApiException(__('Payment gateway request failed'));
}
return [
'type' => 1,
'data' => $source['redirect']['url']
];
}
public function notify($params)
{
\Stripe\Stripe::setApiKey($this->config['stripe_sk_live']);
try {
$event = \Stripe\Webhook::constructEvent(
get_request_content(),
request()->header('HTTP_STRIPE_SIGNATURE'),
$this->config['stripe_webhook_key']
);
} catch (\Stripe\Error\SignatureVerification $e) {
abort(400);
}
switch ($event->type) {
case 'source.chargeable':
$object = $event->data->object;
\Stripe\Charge::create([
'amount' => $object->amount,
'currency' => $object->currency,
'source' => $object->id,
'metadata' => json_decode($object->metadata, true)
]);
break;
case 'charge.succeeded':
$object = $event->data->object;
if ($object->status === 'succeeded') {
if (!isset($object->metadata->out_trade_no) && !isset($object->source->metadata)) {
return('order error');
}
$metaData = isset($object->metadata->out_trade_no) ? $object->metadata : $object->source->metadata;
$tradeNo = $metaData->out_trade_no;
return [
'trade_no' => $tradeNo,
'callback_no' => $object->id
];
}
break;
default:
throw new ApiException('event is not support');
}
return('success');
}
private function exchange($from, $to)
{
$result = file_get_contents('https://api.exchangerate.host/latest?symbols=' . $to . '&base=' . $from);
$result = json_decode($result, true);
return $result['rates'][$to];
}
}

View File

@ -1,140 +0,0 @@
<?php
namespace App\Payments;
use App\Exceptions\ApiException;
use Stripe\Stripe;
use Stripe\Checkout\Session;
class StripeCheckout {
public function __construct($config)
{
$this->config = $config;
}
public function form()
{
return [
'currency' => [
'label' => '货币单位',
'description' => '',
'type' => 'input',
],
'stripe_sk_live' => [
'label' => 'SK_LIVE',
'description' => 'API 密钥',
'type' => 'input',
],
'stripe_pk_live' => [
'label' => 'PK_LIVE',
'description' => 'API 公钥',
'type' => 'input',
],
'stripe_webhook_key' => [
'label' => 'WebHook 密钥签名',
'description' => '',
'type' => 'input',
],
'stripe_custom_field_name' => [
'label' => '自定义字段名称',
'description' => '例如可设置为“联系方式”,以便及时与客户取得联系',
'type' => 'input',
]
];
}
public function pay($order)
{
$currency = $this->config['currency'];
$exchange = $this->exchange('CNY', strtoupper($currency));
if (!$exchange) {
throw new ApiException(__('Currency conversion has timed out, please try again later'));
}
$customFieldName = isset($this->config['stripe_custom_field_name']) ? $this->config['stripe_custom_field_name'] : 'Contact Infomation';
$params = [
'success_url' => $order['return_url'],
'cancel_url' => $order['return_url'],
'client_reference_id' => $order['trade_no'],
'line_items' => [
[
'price_data' => [
'currency' => $currency,
'product_data' => [
'name' => $order['trade_no']
],
'unit_amount' => floor($order['total_amount'] * $exchange)
],
'quantity' => 1
]
],
'mode' => 'payment',
'invoice_creation' => ['enabled' => true],
'phone_number_collection' => ['enabled' => true],
'custom_fields' => [
[
'key' => 'contactinfo',
'label' => ['type' => 'custom', 'custom' => $customFieldName],
'type' => 'text',
],
],
// 'customer_email' => $user['email'] not support
];
Stripe::setApiKey($this->config['stripe_sk_live']);
try {
$session = Session::create($params);
} catch (\Exception $e) {
info($e);
throw new ApiException("Failed to create order. Error: {$e->getMessage}");
}
return [
'type' => 1, // 0:qrcode 1:url
'data' => $session->url
];
}
public function notify($params)
{
\Stripe\Stripe::setApiKey($this->config['stripe_sk_live']);
try {
$event = \Stripe\Webhook::constructEvent(
get_request_content(),
request()->header('HTTP_STRIPE_SIGNATURE'),
$this->config['stripe_webhook_key']
);
} catch (\Stripe\Error\SignatureVerification $e) {
abort(400);
}
switch ($event->type) {
case 'checkout.session.completed':
$object = $event->data->object;
if ($object->payment_status === 'paid') {
return [
'trade_no' => $object->client_reference_id,
'callback_no' => $object->payment_intent
];
}
break;
case 'checkout.session.async_payment_succeeded':
$object = $event->data->object;
return [
'trade_no' => $object->client_reference_id,
'callback_no' => $object->payment_intent
];
break;
default:
throw new ApiException('event is not support');
}
return('success');
}
private function exchange($from, $to)
{
$result = file_get_contents('https://api.exchangerate.host/latest?symbols=' . $to . '&base=' . $from);
$result = json_decode($result, true);
return $result['rates'][$to];
}
}

View File

@ -1,126 +0,0 @@
<?php
/**
* 自己写别抄抄NMB抄
*/
namespace App\Payments;
use App\Exceptions\ApiException;
use Stripe\Source;
use Stripe\Stripe;
class StripeCredit {
public function __construct($config)
{
$this->config = $config;
}
public function form()
{
return [
'currency' => [
'label' => '货币单位',
'description' => '',
'type' => 'input',
],
'stripe_sk_live' => [
'label' => 'SK_LIVE',
'description' => '',
'type' => 'input',
],
'stripe_pk_live' => [
'label' => 'PK_LIVE',
'description' => '',
'type' => 'input',
],
'stripe_webhook_key' => [
'label' => 'WebHook密钥签名',
'description' => '',
'type' => 'input',
]
];
}
public function pay($order)
{
info($order);
$currency = $this->config['currency'];
$exchange = $this->exchange('CNY', strtoupper($currency));
if (!$exchange) {
throw new ApiException(__('Currency conversion has timed out, please try again later'));
}
Stripe::setApiKey($this->config['stripe_sk_live']);
try {
$charge = \Stripe\Charge::create([
'amount' => floor($order['total_amount'] * $exchange),
'currency' => $currency,
'source' => $order['stripe_token'],
'metadata' => [
'user_id' => $order['user_id'],
'out_trade_no' => $order['trade_no'],
'identifier' => ''
]
]);
} catch (\Exception $e) {
info($e);
throw new ApiException(__('Payment failed. Please check your credit card information'));
}
if (!$charge->paid) {
throw new ApiException(__('Payment failed. Please check your credit card information'));
}
return [
'type' => 2,
'data' => $charge->paid
];
}
public function notify($params)
{
\Stripe\Stripe::setApiKey($this->config['stripe_sk_live']);
try {
$event = \Stripe\Webhook::constructEvent(
get_request_content(),
request()->header('HTTP_STRIPE_SIGNATURE'),
$this->config['stripe_webhook_key']
);
} catch (\Stripe\Error\SignatureVerification $e) {
\Log::error($e);
abort(400);
}
switch ($event->type) {
case 'source.chargeable':
$object = $event->data->object;
\Stripe\Charge::create([
'amount' => $object->amount,
'currency' => $object->currency,
'source' => $object->id,
'metadata' => json_decode($object->metadata, true)
]);
break;
case 'charge.succeeded':
$object = $event->data->object;
if ($object->status === 'succeeded') {
if (!isset($object->metadata->out_trade_no) && !isset($object->source->metadata)) {
return('order error');
}
$metaData = isset($object->metadata->out_trade_no) ? $object->metadata : $object->source->metadata;
$tradeNo = $metaData->out_trade_no;
return [
'trade_no' => $tradeNo,
'callback_no' => $object->id
];
}
break;
default:
throw new ApiException('event is not support');
}
return('success');
}
private function exchange($from, $to)
{
$result = file_get_contents('https://api.exchangerate.host/latest?symbols=' . $to . '&base=' . $from);
$result = json_decode($result, true);
return $result['rates'][$to];
}
}

View File

@ -1,118 +0,0 @@
<?php
/**
* 自己写别抄抄NMB抄
*/
namespace App\Payments;
use App\Exceptions\ApiException;
use Stripe\Source;
use Stripe\Stripe;
class StripeWepay {
public function __construct($config)
{
$this->config = $config;
}
public function form()
{
return [
'currency' => [
'label' => '货币单位',
'description' => '',
'type' => 'input',
],
'stripe_sk_live' => [
'label' => 'SK_LIVE',
'description' => '',
'type' => 'input',
],
'stripe_webhook_key' => [
'label' => 'WebHook密钥签名',
'description' => '',
'type' => 'input',
]
];
}
public function pay($order)
{
$currency = $this->config['currency'];
$exchange = $this->exchange('CNY', strtoupper($currency));
if (!$exchange) {
throw new ApiException(__('Currency conversion has timed out, please try again later'));
}
Stripe::setApiKey($this->config['stripe_sk_live']);
$source = Source::create([
'amount' => floor($order['total_amount'] * $exchange),
'currency' => $currency,
'type' => 'wechat',
'statement_descriptor' => $order['trade_no'],
'metadata' => [
'user_id' => $order['user_id'],
'out_trade_no' => $order['trade_no'],
'identifier' => ''
],
'redirect' => [
'return_url' => $order['return_url']
]
]);
if (!$source['wechat']['qr_code_url']) {
throw new ApiException(__('Payment gateway request failed'));
}
return [
'type' => 0,
'data' => $source['wechat']['qr_code_url']
];
}
public function notify($params)
{
\Stripe\Stripe::setApiKey($this->config['stripe_sk_live']);
try {
$event = \Stripe\Webhook::constructEvent(
get_request_content(),
request()->header('HTTP_STRIPE_SIGNATURE'),
$this->config['stripe_webhook_key']
);
} catch (\Stripe\Error\SignatureVerification $e) {
abort(400);
}
switch ($event->type) {
case 'source.chargeable':
$object = $event->data->object;
\Stripe\Charge::create([
'amount' => $object->amount,
'currency' => $object->currency,
'source' => $object->id,
'metadata' => json_decode($object->metadata, true)
]);
break;
case 'charge.succeeded':
$object = $event->data->object;
if ($object->status === 'succeeded') {
if (!isset($object->metadata->out_trade_no) && !isset($object->source->metadata)) {
return('order error');
}
$metaData = isset($object->metadata->out_trade_no) ? $object->metadata : $object->source->metadata;
$tradeNo = $metaData->out_trade_no;
return [
'trade_no' => $tradeNo,
'callback_no' => $object->id
];
}
break;
default:
throw new ApiException('event is not support');
}
return('success');
}
private function exchange($from, $to)
{
$result = file_get_contents('https://api.exchangerate.host/latest?symbols=' . $to . '&base=' . $from);
$result = json_decode($result, true);
return $result['rates'][$to];
}
}

View File

@ -1,85 +0,0 @@
<?php
namespace App\Payments;
use App\Exceptions\ApiException;
use Omnipay\Omnipay;
use Omnipay\WechatPay\Helper;
class WechatPayNative {
public function __construct($config)
{
$this->config = $config;
}
public function form()
{
return [
'app_id' => [
'label' => 'APPID',
'description' => '绑定微信支付商户的APPID',
'type' => 'input',
],
'mch_id' => [
'label' => '商户号',
'description' => '微信支付商户号',
'type' => 'input',
],
'api_key' => [
'label' => 'APIKEY(v1)',
'description' => '',
'type' => 'input',
]
];
}
public function pay($order)
{
$gateway = Omnipay::create('WechatPay_Native');
$gateway->setAppId($this->config['app_id']);
$gateway->setMchId($this->config['mch_id']);
$gateway->setApiKey($this->config['api_key']);
$gateway->setNotifyUrl($order['notify_url']);
$params = [
'body' => $order['trade_no'],
'out_trade_no' => $order['trade_no'],
'total_fee' => $order['total_amount'],
'spbill_create_ip' => '0.0.0.0',
'fee_type' => 'CNY'
];
$request = $gateway->purchase($params);
$response = $request->send();
$response = $response->getData();
if ($response['return_code'] !== 'SUCCESS') {
throw new ApiException($response['return_msg']);
}
return [
'type' => 0,
'data' => $response['code_url'],
'custom_result' => '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'
];
}
public function notify($params)
{
$data = Helper::xml2array(get_request_content());
$gateway = Omnipay::create('WechatPay');
$gateway->setAppId($this->config['app_id']);
$gateway->setMchId($this->config['mch_id']);
$gateway->setApiKey($this->config['api_key']);
$response = $gateway->completePurchase([
'request_params' => get_request_content()
])->send();
if (!$response->isPaid()) {
return('FAIL');
}
return [
'trade_no' => $data['out_trade_no'],
'callback_no' => $data['transaction_id']
];
}
}

View File

@ -4,6 +4,7 @@ namespace App\Providers;
use App\Support\Setting;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Foundation\Application;
class SettingServiceProvider extends ServiceProvider
{
@ -14,9 +15,11 @@ class SettingServiceProvider extends ServiceProvider
*/
public function register()
{
$this->app->bind('setting', function ($app) {
return Setting::fromDatabase(); // 假设 AdminSetting 是您的设置类
$this->app->bind(Setting::class, function (Application $app) {
return new Setting();
});
}
/**
@ -26,6 +29,5 @@ class SettingServiceProvider extends ServiceProvider
*/
public function boot()
{
//
}
}

View File

@ -11,11 +11,15 @@ class MailService
{
public function remindTraffic(User $user)
{
if (!$user->remind_traffic) return;
if (!$this->remindTrafficIsWarnValue($user->u, $user->d, $user->transfer_enable)) return;
if (!$user->remind_traffic)
return;
if (!$this->remindTrafficIsWarnValue($user->u, $user->d, $user->transfer_enable))
return;
$flag = CacheKey::get('LAST_SEND_EMAIL_REMIND_TRAFFIC', $user->id);
if (Cache::get($flag)) return;
if (!Cache::put($flag, 1, 24 * 3600)) return;
if (Cache::get($flag))
return;
if (!Cache::put($flag, 1, 24 * 3600))
return;
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => __('The traffic usage in :app_name has reached 80%', [
@ -31,7 +35,8 @@ class MailService
public function remindExpire(User $user)
{
if (!($user->expired_at !== NULL && ($user->expired_at - 86400) < time() && $user->expired_at > time())) return;
if (!($user->expired_at !== NULL && ($user->expired_at - 86400) < time() && $user->expired_at > time()))
return;
SendEmailJob::dispatch([
'email' => $user->email,
'subject' => __('The service in :app_name is about to expire', [
@ -48,11 +53,67 @@ class MailService
private function remindTrafficIsWarnValue($u, $d, $transfer_enable)
{
$ud = $u + $d;
if (!$ud) return false;
if (!$transfer_enable) return false;
if (!$ud)
return false;
if (!$transfer_enable)
return false;
$percentage = ($ud / $transfer_enable) * 100;
if ($percentage < 80) return false;
if ($percentage >= 100) return false;
if ($percentage < 80)
return false;
if ($percentage >= 100)
return false;
return true;
}
/**
* 发送邮件
*
* @param array $params 包含邮件参数的数组,必须包含以下字段:
* - email: 收件人邮箱地址
* - subject: 邮件主题
* - template_name: 邮件模板名称,例如 "welcome" "password_reset"
* - template_value: 邮件模板变量,一个关联数组,包含模板中需要替换的变量和对应的值
* @return array 包含邮件发送结果的数组,包含以下字段:
* - email: 收件人邮箱地址
* - subject: 邮件主题
* - template_name: 邮件模板名称
* - error: 如果邮件发送失败,包含错误信息;否则为 null
* @throws \InvalidArgumentException 如果 $params 参数缺少必要的字段,抛出此异常
*/
public static function sendEmail(array $params)
{
if (admin_setting('email_host')) {
\Config::set('mail.host', admin_setting('email_host', config('mail.host')));
\Config::set('mail.port', admin_setting('email_port', config('mail.port')));
\Config::set('mail.encryption', admin_setting('email_encryption', config('mail.encryption')));
\Config::set('mail.username', admin_setting('email_username', config('mail.username')));
\Config::set('mail.password', admin_setting('email_password', config('mail.password')));
\Config::set('mail.from.address', admin_setting('email_from_address', config('mail.from.address')));
\Config::set('mail.from.name', admin_setting('app_name', 'XBoard'));
}
$email = $params['email'];
$subject = $params['subject'];
$params['template_name'] = 'mail.' . admin_setting('email_template', 'default') . '.' . $params['template_name'];
try {
\Mail::send(
$params['template_name'],
$params['template_value'],
function ($message) use ($email, $subject) {
$message->to($email)->subject($subject);
}
);
$error = null;
} catch (\Exception $e) {
$error = $e->getMessage();
}
$log = [
'email' => $params['email'],
'subject' => $params['subject'],
'template_name' => $params['template_name'],
'error' => $error,
'config' => config('mail')
];
\App\Models\MailLog::create($log);
return $log;
}
}

View File

@ -40,7 +40,7 @@ class OrderService
DB::beginTransaction();
if ($order->surplus_order_ids) {
Order::whereIn('id', $order->surplus_order_ids)->update([
'status' => 4
'status' => Order::STATUS_DISCOUNTED
]);
}
switch ((string)$order->period) {
@ -71,7 +71,7 @@ class OrderService
if (!$this->user->save()) {
throw new \Exception('用户信息保存失败');
}
$order->status = 3;
$order->status = Order::STATUS_COMPLETED;
if (!$order->save()) {
throw new \Exception('订单信息保存失败');
}
@ -88,10 +88,10 @@ class OrderService
{
$order = $this->order;
if ($order->period === 'reset_price') {
$order->type = 4;
$order->type = Order::TYPE_RESET_TRAFFIC;
} else if ($user->plan_id !== NULL && $order->plan_id !== $user->plan_id && ($user->expired_at > time() || $user->expired_at === NULL)) {
if (!(int)admin_setting('plan_change_enable', 1)) throw new ApiException('目前不允许更改订阅,请联系客服或提交工单操作');
$order->type = 3;
$order->type = Order::TYPE_UPGRADE;
if ((int)admin_setting('surplus_enable', 1)) $this->getSurplusValue($user, $order);
if ($order->surplus_amount >= $order->total_amount) {
$order->refund_amount = $order->surplus_amount - $order->total_amount;
@ -100,9 +100,9 @@ class OrderService
$order->total_amount = $order->total_amount - $order->surplus_amount;
}
} else if ($user->expired_at > time() && $order->plan_id == $user->plan_id) { // 用户订阅未过期且购买订阅与当前订阅相同 === 续费
$order->type = 2;
$order->type = Order::TYPE_RENEWAL;
} else { // 新购
$order->type = 1;
$order->type = Order::TYPE_NEW_PURCHASE;
}
}
@ -215,8 +215,8 @@ class OrderService
public function paid(string $callbackNo)
{
$order = $this->order;
if ($order->status !== 0) return true;
$order->status = 1;
if ($order->status !== Order::STATUS_PENDING) return true;
$order->status = Order::STATUS_PROCESSING;
$order->paid_at = time();
$order->callback_no = $callbackNo;
if (!$order->save()) return false;
@ -233,7 +233,7 @@ class OrderService
$order = $this->order;
try {
DB::beginTransaction();
$order->status = 2;
$order->status = Order::STATUS_CANCELLED;
if (!$order->save()) {
throw new \Exception('Failed to save order status.');
}

View File

@ -17,7 +17,8 @@ use Illuminate\Support\Facades\Cache;
class ServerService
{
public function getAvailableVless(User $user):array
// 获取可用的 VLESS 服务器列表
public static function getAvailableVless(User $user): array
{
$servers = [];
$model = ServerVless::orderBy('sort', 'ASC');
@ -45,11 +46,11 @@ class ServerService
$servers[] = $serverData;
}
return $servers;
}
public function getAvailableVmess(User $user):array
// 获取可用的 VMESS 服务器列表
public static function getAvailableVmess(User $user): array
{
$servers = [];
$model = ServerVmess::orderBy('sort', 'ASC');
@ -69,11 +70,11 @@ class ServerService
$servers[] = $vmess[$key]->toArray();
}
return $servers;
}
public function getAvailableTrojan(User $user):array
// 获取可用的 TROJAN 服务器列表
public static function getAvailableTrojan(User $user): array
{
$servers = [];
$model = ServerTrojan::orderBy('sort', 'ASC');
@ -95,7 +96,8 @@ class ServerService
return $servers;
}
public function getAvailableHysteria(User $user)
// 获取可用的 HYSTERIA 服务器列表
public static function getAvailableHysteria(User $user)
{
$availableServers = [];
$model = ServerHysteria::orderBy('sort', 'ASC');
@ -119,7 +121,8 @@ class ServerService
return $availableServers;
}
public function getAvailableShadowsocks(User $user)
// 获取可用的 SHADOWSOCKS 服务器列表
public static function getAvailableShadowsocks(User $user)
{
$servers = [];
$model = ServerShadowsocks::orderBy('sort', 'ASC');
@ -141,15 +144,16 @@ class ServerService
return $servers;
}
public function getAvailableServers(User $user)
// 获取可用的服务器列表
public static function getAvailableServers(User $user)
{
$servers = Cache::remember('serversAvailable_'. $user->id, 5, function() use($user){
return array_merge(
$this->getAvailableShadowsocks($user),
$this->getAvailableVmess($user),
$this->getAvailableTrojan($user),
$this->getAvailableHysteria($user),
$this->getAvailableVless($user)
self::getAvailableShadowsocks($user),
self::getAvailableVmess($user),
self::getAvailableTrojan($user),
self::getAvailableHysteria($user),
self::getAvailableVless($user)
);
});
$tmp = array_column($servers, 'sort');
@ -162,7 +166,8 @@ class ServerService
}, $servers);
}
public function getAvailableUsers($groupId): Collection
// 获取可用的用户列表
public static function getAvailableUsers($groupId): Collection
{
return \DB::table('v2_user')
->whereIn('group_id', $groupId)
@ -180,7 +185,8 @@ class ServerService
->get();
}
public function log(int $userId, int $serverId, int $u, int $d, float $rate, string $method)
// 记录流量日志
public static function log(int $userId, int $serverId, int $u, int $d, float $rate, string $method)
{
if (($u + $d) < 10240) return true;
$timestamp = strtotime(date('Y-m-d'));
@ -212,7 +218,8 @@ class ServerService
}
}
public function getAllShadowsocks()
// 获取所有 SHADOWSOCKS 服务器列表
public static function getAllShadowsocks()
{
$servers = ServerShadowsocks::orderBy('sort', 'ASC')
->get()
@ -223,7 +230,8 @@ class ServerService
return $servers;
}
public function getAllVMess()
// 获取所有 VMESS 服务器列表
public static function getAllVMess()
{
$servers = ServerVmess::orderBy('sort', 'ASC')
->get()
@ -234,7 +242,8 @@ class ServerService
return $servers;
}
public function getAllVLess()
// 获取所有 VLESS 服务器列表
public static function getAllVLess()
{
$servers = ServerVless::orderBy('sort', 'ASC')
->get()
@ -245,7 +254,8 @@ class ServerService
return $servers;
}
public function getAllTrojan()
// 获取所有 TROJAN 服务器列表
public static function getAllTrojan()
{
$servers = ServerTrojan::orderBy('sort', 'ASC')
->get()
@ -256,7 +266,8 @@ class ServerService
return $servers;
}
public function getAllHysteria()
// 获取所有 HYSTERIA 服务器列表
public static function getAllHysteria()
{
$servers = ServerHysteria::orderBy('sort', 'ASC')
->get()
@ -267,7 +278,8 @@ class ServerService
return $servers;
}
private function mergeData(&$servers)
// 合并数据
private static function mergeData(&$servers)
{
foreach ($servers as $k => $v) {
$serverType = strtoupper($v['type']);
@ -291,22 +303,24 @@ class ServerService
}
}
public function getAllServers()
// 获取所有服务器列表
public static function getAllServers()
{
$servers = array_merge(
$this->getAllShadowsocks(),
$this->getAllVMess(),
$this->getAllTrojan(),
$this->getAllHysteria(),
$this->getAllVLess()
self::getAllShadowsocks(),
self::getAllVMess(),
self::getAllTrojan(),
self::getAllHysteria(),
self::getAllVLess()
);
$this->mergeData($servers);
self::mergeData($servers);
$tmp = array_column($servers, 'sort');
array_multisort($tmp, SORT_ASC, $servers);
return $servers;
}
public function getRoutes(array $routeIds)
// 获取路由规则
public static function getRoutes(array $routeIds)
{
$routes = ServerRoute::select(['id', 'match', 'action', 'action_value'])->whereIn('id', $routeIds)->get();
// TODO: remove on 1.8.0
@ -318,7 +332,8 @@ class ServerService
return $routes;
}
public function getServer($serverId, $serverType)
// 获取服务器
public static function getServer($serverId, $serverType)
{
switch ($serverType) {
case 'vmess':
@ -336,8 +351,8 @@ class ServerService
}
}
// 根据节点IP和父级别节点ID查询节点
public function getChildServer($serverId, $serverType, $nodeIp){
// 根据节点IP和父级别节点ID查询节点
public static function getChildServer($serverId, $serverType, $nodeIp){
switch ($serverType) {
case 'vmess':
return ServerVmess::query()

View File

@ -21,9 +21,9 @@ class TicketService {
'message' => $message
]);
if ($userId !== $ticket->user_id) {
$ticket->reply_status = 0;
$ticket->reply_status = Ticket::STATUS_OPENING;
} else {
$ticket->reply_status = 1;
$ticket->reply_status = Ticket::STATUS_CLOSED;
}
if (!$ticketMessage || !$ticket->save()) {
throw new \Exception();
@ -43,7 +43,7 @@ class TicketService {
if (!$ticket) {
throw new ApiException('工单不存在');
}
$ticket->status = 0;
$ticket->status = Ticket::STATUS_OPENING;
try{
DB::beginTransaction();
$ticketMessage = TicketMessage::create([
@ -52,9 +52,9 @@ class TicketService {
'message' => $message
]);
if ($userId !== $ticket->user_id) {
$ticket->reply_status = 0;
$ticket->reply_status = Ticket::STATUS_OPENING;
} else {
$ticket->reply_status = 1;
$ticket->reply_status = Ticket::STATUS_CLOSED;
}
if (!$ticketMessage || !$ticket->save()) {
throw new ApiException('工单回复失败');

View File

@ -2,8 +2,6 @@
namespace App\Services;
use App\Jobs\StatServerJob;
use App\Jobs\StatUserJob;
use App\Jobs\TrafficFetchJob;
use App\Models\Order;
use App\Models\Plan;
@ -172,7 +170,7 @@ class UserService
{
// 获取子节点
$childServer = ($server['parent_id'] == null && !blank($nodeIp))
? (new ServerService())->getChildServer($server['id'], $protocol, $nodeIp)
? ServerService::getChildServer($server['id'], $protocol, $nodeIp)
: null;
$timestamp = strtotime(date('Y-m-d'));
collect($data)->chunk(1000)->each(function($chunk) use ($timestamp,$server,$protocol, $childServer){

View File

@ -10,6 +10,10 @@ use Illuminate\Support\Fluent;
class Setting extends Fluent
{
public function __construct()
{
$this->attributes = self::fromDatabase();
}
/**
* 获取配置,并转化为数组.
*
@ -129,21 +133,12 @@ class Setting extends Fluent
public static function fromDatabase()
{
$values = [];
try {
if(env('ADMIN_SETTING_CACHE') > 0){
$values = Cache::remember('admin_settings', env('ADMIN_SETTING_CACHE'), function () {
$values = Cache::remember('admin_settings', env('ADMIN_SETTING_CACHE', 0), function () {
return SettingModel::pluck('value', 'name')->toArray();
}
);
}else{
$values = SettingModel::pluck('value', 'name')->toArray();
}
});
} catch (QueryException $e) {
return new static($values);
// throw new \Exception('配置获取失败、请检查数据库配置');
}
return new static($values);
return $values;
}
}

View File

@ -22,10 +22,8 @@
"joanhey/adapterman": "^0.6.1",
"laravel/framework": "^10.0",
"laravel/horizon": "^5.9.6",
"laravel/pulse": "*",
"laravel/tinker": "^2.5",
"linfo/linfo": "^4.0",
"nunomaduro/collision": "^7.10",
"paragonie/sodium_compat": "^1.20",
"php-curl-class/php-curl-class": "^8.6",
"spatie/db-dumper": "^3.4",
@ -39,6 +37,7 @@
"barryvdh/laravel-debugbar": "^3.9",
"fakerphp/faker": "^1.9.1",
"mockery/mockery": "^1.3.1",
"nunomaduro/collision": "^7.10",
"orangehill/iseed": "^3.0",
"phpunit/phpunit": "^10.0",
"spatie/laravel-ignition": "^2.0"