diff --git a/app/Components/Helpers.php b/app/Components/Helpers.php index b54b2998..9fb6f86d 100644 --- a/app/Components/Helpers.php +++ b/app/Components/Helpers.php @@ -252,20 +252,4 @@ class Helpers return $log->save(); } - - public static function abortIfNotModified($data): string - { - $req = request(); - // Only for "GET" method - if (! $req->isMethod('GET')) { - return ''; - } - - $etag = sha1(json_encode($data)); - if ($etag == $req->header('IF-NONE-MATCH')) { - abort(304); - } - - return $etag; - } } diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 1997a3c6..a9724ac5 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -78,23 +78,23 @@ class Handler extends ExceptionHandler case $exception instanceof NotFoundHttpException: // 捕获访问异常 Log::info('异常请求:'.$request->fullUrl().',IP:'.IP::getClientIp()); - if ($request->ajax()) { - return Response::json(['status' => 'fail', 'message' => trans('error.MissingPage')]); + if ($request->ajax() || $request->wantsJson()) { + return Response::json(['status' => 'fail', 'message' => trans('error.MissingPage')], 404); } return Response::view('auth.error', ['message' => trans('error.MissingPage')], 404); case $exception instanceof AuthenticationException: // 捕获身份校验异常 - if ($request->ajax()) { - return Response::json(['status' => 'fail', 'message' => trans('error.Unauthorized')]); + if ($request->ajax() || $request->wantsJson()) { + return Response::json(['status' => 'fail', 'message' => trans('error.Unauthorized')], 401); } return Response::view('auth.error', ['message' => trans('error.Unauthorized')], 401); case $exception instanceof TokenMismatchException: // 捕获CSRF异常 - if ($request->ajax()) { + if ($request->ajax() || $request->wantsJson()) { return Response::json([ 'status' => 'fail', 'message' => trans('error.RefreshPage').''.trans('error.Refresh').'', - ]); + ], 419); } return Response::view( @@ -103,17 +103,17 @@ class Handler extends ExceptionHandler 419 ); case $exception instanceof ReflectionException: - if ($request->ajax()) { - return Response::json(['status' => 'fail', 'message' => trans('error.SystemError')]); + if ($request->ajax() || $request->wantsJson()) { + return Response::json(['status' => 'fail', 'message' => trans('error.SystemError')], 500); } return Response::view('auth.error', ['message' => trans('error.SystemError')], 500); case $exception instanceof ErrorException: // 捕获系统错误异常 - if ($request->ajax()) { + if ($request->ajax() || $request->wantsJson()) { return Response::json([ 'status' => 'fail', 'message' => trans('error.SystemError').', '.trans('error.Visit').''.trans('error.log').'', - ]); + ], 500); } return Response::view( @@ -122,20 +122,14 @@ class Handler extends ExceptionHandler 500 ); case $exception instanceof ConnectionException: - if ($request->ajax()) { - return Response::json([ - 'status' => 'fail', - 'message' => $exception->getMessage(), - ]); + if ($request->ajax() || $request->wantsJson()) { + return Response::json(['status' => 'fail', 'message' => $exception->getMessage()], 408); } return Response::view('auth.error', ['message' => $exception->getMessage()], 408); default: - if ($request->ajax()) { - return Response::json([ - 'status' => 'fail', - 'message' => $exception->getMessage(), - ]); + if ($request->ajax() || $request->wantsJson()) { + return Response::json(['status' => 'fail', 'message' => $exception->getMessage()]); } return Response::view('auth.error', ['message' => $exception->getMessage()]); diff --git a/app/Http/Controllers/Api/WebApi/BaseController.php b/app/Http/Controllers/Api/WebApi/BaseController.php index 13646c41..8a6f9398 100644 --- a/app/Http/Controllers/Api/WebApi/BaseController.php +++ b/app/Http/Controllers/Api/WebApi/BaseController.php @@ -2,96 +2,86 @@ namespace App\Http\Controllers\Api\WebApi; -use App\Components\Helpers; use App\Models\Node; -use App\Models\NodeHeartBeat; -use App\Models\NodeOnlineIp; -use App\Models\NodeOnlineLog; -use App\Models\RuleLog; -use App\Models\User; -use App\Models\UserDataFlowLog; -use Arr; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Response; +use Validator; class BaseController { // 上报节点心跳信息 - public function setNodeStatus(Request $request, $id): JsonResponse + public function setNodeStatus(Request $request, Node $node): JsonResponse { - $cpu = (int) $request->input('cpu') / 100; - $mem = (int) $request->input('mem') / 100; - $disk = (int) $request->input('disk') / 100; + $validator = Validator::make($request->all(), ['cpu' => 'required', 'mem' => 'required', 'disk' => 'required', 'uptime' => 'required|numeric']); - if (is_null($request->input('uptime'))) { + if ($validator->fails()) { return $this->returnData('上报节点心跳信息失败,请检查字段'); } - $obj = new NodeHeartBeat(); - $obj->node_id = $id; - $obj->uptime = (int) $request->input('uptime'); - //$obj->load = $request->input('load'); - $obj->load = implode(' ', [$cpu, $mem, $disk]); - $obj->log_time = time(); - $obj->save(); + $data = array_map('intval', $validator->validated()); - if (! $obj->id) { - return $this->returnData('生成节点心跳信息失败'); + if ($node->heartBeats()->create([ + 'uptime' => $data['uptime'], + 'load' => implode(' ', [$data['cpu'] / 100, $data['mem'] / 100, $data['disk'] / 100]), + 'log_time' => time(), + ])) { + return $this->returnData('上报节点心跳信息成功', 'success', 200); } - return $this->returnData('上报节点心跳信息成功', 'success', 200); + return $this->returnData('生成节点心跳信息失败'); } // 返回数据 - public function returnData($message, $status = 'fail', $code = 400, $data = [], $addition = []): JsonResponse + public function returnData(string $message, string $status = 'fail', int $code = 400, array $data = [], array $addition = null): JsonResponse { - $etag = Helpers::abortIfNotModified($data); - $data = [ - 'status' => $status, - 'code' => $code, - 'data' => $data, - 'message' => $message, - ]; + $etag = self::abortIfNotModified($data); + $data = ['status' => $status, 'code' => $code, 'data' => $data, 'message' => $message]; - if ($addition) { + if (isset($addition)) { $data = array_merge($data, $addition); } - return Response::json($data)->header('ETAG', $etag); + return Response::json($data)->header('ETAG', $etag)->setStatusCode($code); } - // 上报节点在线人数 - public function setNodeOnline(Request $request, $id): JsonResponse + // 检查数据是否有变动 + private static function abortIfNotModified($data): string { - $inputArray = $request->all(); + $req = request(); + // Only for "GET" method + if (! $req->isMethod('GET')) { + return ''; + } + + $etag = sha1(json_encode($data)); + if ($etag === $req->header('IF-NONE-MATCH')) { + abort(304); + } + + return $etag; + } + + // 上报节点在线IP + public function setNodeOnline(Request $request, Node $node): JsonResponse + { + $validator = Validator::make($request->all(), ['*.uid' => 'required|numeric|exists:user,id', '*.ip' => 'required|string']); + + if ($validator->fails()) { + return $this->returnData('上报节点在线用户IP信息失败,请检查字段'); + } + $onlineCount = 0; - foreach ($inputArray as $input) { - if (! Arr::has($input, ['ip', 'uid'])) { - return $this->returnData('上报节点在线用户IP信息失败,请检查字段'); - } - - $obj = new NodeOnlineIp(); - $obj->node_id = $id; - $obj->user_id = $input['uid']; - $obj->ip = $input['ip']; - $obj->port = User::find($input['uid'])->port; - $obj->created_at = time(); - $obj->save(); - - if (! $obj->id) { - return $this->returnData('生成节点在线用户IP信息失败'); - } + foreach ($validator->validated() as $input) { // 处理节点在线IP数据 + $formattedData[] = ['user_id' => $input['uid'], 'ip' => $input['ip'], 'created_at' => time()]; $onlineCount++; } - $obj = new NodeOnlineLog(); - $obj->node_id = $id; - $obj->online_user = $onlineCount; - $obj->log_time = time(); - $obj->save(); + if (isset($formattedData) && ! $node->onlineIps()->createMany($formattedData)) { // 生成节点在线IP数据 + return $this->returnData('生成节点在线用户IP信息失败'); + } - if ($obj->id) { + if ($node->onlineLogs()->create(['online_user' => $onlineCount, 'log_time' => time()])) { // 生成节点在线人数数据 return $this->returnData('上报节点在线情况成功', 'success', 200); } @@ -99,47 +89,39 @@ class BaseController } // 上报用户流量日志 - public function setUserTraffic(Request $request, $id): JsonResponse + public function setUserTraffic(Request $request, Node $node): JsonResponse { - foreach ($request->all() as $input) { - if (! Arr::exists($input, 'uid')) { - return $this->returnData('上报用户流量日志失败,请检查字段'); - } + $validator = Validator::make($request->all(), ['*.uid' => 'required|numeric|exists:user,id', '*.upload' => 'required|numeric', '*.download' => 'required|numeric']); - $rate = Node::find($id)->traffic_rate; - - $log = new UserDataFlowLog(); - $log->user_id = (int) $input['uid']; - $log->u = (int) $input['upload'] * $rate; - $log->d = (int) $input['download'] * $rate; - $log->node_id = $id; - $log->rate = $rate; - $log->traffic = flowAutoShow($log->u + $log->d); - $log->log_time = time(); - $log->save(); - - if (! $log->id) { - return $this->returnData('生成用户流量日志失败'); - } - $user = User::find($log->user_id); - if ($user) { - $user->u += $log->u; - $user->d += $log->d; - $user->t = time(); - $user->save(); - } + if ($validator->fails()) { + return $this->returnData('上报用户流量日志失败,请检查字段'); } - return $this->returnData('上报用户流量日志成功', 'success', 200); + foreach ($validator->validated() as $input) { // 处理用户流量数据 + $rate = $node->traffic_rate; + $u = $input['upload'] * $rate; + $d = $input['download'] * $rate; + + $formattedData[] = ['user_id' => $input['uid'], 'u' => $u, 'd' => $d, 'rate' => $rate, 'traffic' => flowAutoShow($u + $d), 'log_time' => time()]; + } + + if (isset($formattedData) && $logs = $node->userDataFlowLogs()->createMany($formattedData)) { // 生成用户流量数据 + foreach ($logs as $log) { // 更新用户流量数据 + $user = $log->user; + $user->update(['u' => $user->u + $log->u, 'd' => $user->d + $log->d, 't' => time()]); + } + + return $this->returnData('上报用户流量日志成功', 'success', 200); + } + + return $this->returnData('生成用户流量日志失败'); } // 获取节点的审计规则 public function getNodeRule(Node $node): JsonResponse { - $data = []; - //节点未设置任何审计规则 - if ($node->rule_group_id) { - $ruleGroup = $node->ruleGroup; + // 节点未设置任何审计规则 + if ($ruleGroup = $node->ruleGroup) { foreach ($ruleGroup->rules as $rule) { $data[] = [ 'id' => $rule->id, @@ -148,26 +130,24 @@ class BaseController ]; } - return $this->returnData('获取节点审计规则成功', 'success', 200, ['mode' => $ruleGroup->type ? 'reject' : 'allow', 'rules' => $data]); + return $this->returnData('获取节点审计规则成功', 'success', 200, ['mode' => $ruleGroup->type ? 'reject' : 'allow', 'rules' => $data ?? []]); } - //放行 - return $this->returnData('获取节点审计规则成功', 'success', 200, ['mode' => 'all', 'rules' => $data]); + // 放行 + return $this->returnData('获取节点审计规则成功', 'success', 200, ['mode' => 'all', 'rules' => $data ?? []]); } - // 上报用户触发的审计规则记录 - public function addRuleLog(Request $request, $id): JsonResponse + // 上报用户触发审计规则记录 + public function addRuleLog(Request $request, Node $node): JsonResponse { - if ($request->has(['uid', 'rule_id', 'reason'])) { - $obj = new RuleLog(); - $obj->user_id = $request->input('uid'); - $obj->node_id = $id; - $obj->rule_id = $request->input('rule_id'); - $obj->reason = $request->input('reason'); + $validator = Validator::make($request->all(), ['uid' => 'required|numeric|exists:user,id', 'rule_id' => 'required|numeric|exists:rule,id', 'reason' => 'required']); - if ($obj->save()) { - return $this->returnData('上报用户触发审计规则日志成功', 'success', 200); - } + if ($validator->fails()) { + return $this->returnData('上报用户触发审计规则日志失败,请检查字段'); + } + $data = $validator->validated(); + if ($node->ruleLogs()->create(['user_id' => $data['uid'], 'rule_id' => $data['rule_id'], 'reason' => $data['reason']])) { + return $this->returnData('上报用户触发审计规则日志成功', 'success', 200); } return $this->returnData('上报用户触发审计规则日志失败'); diff --git a/app/Http/Controllers/Api/WebApi/VNetController.php b/app/Http/Controllers/Api/WebApi/VNetController.php index 0d877760..af642751 100644 --- a/app/Http/Controllers/Api/WebApi/VNetController.php +++ b/app/Http/Controllers/Api/WebApi/VNetController.php @@ -45,10 +45,6 @@ class VNetController extends BaseController ]; } - if (isset($data)) { - return $this->returnData('获取用户列表成功', 'success', 200, $data, ['updateTime' => time()]); - } - - return $this->returnData('获取用户列表失败'); + return $this->returnData('获取用户列表成功', 'success', 200, $data ?? [], ['updateTime' => time()]); } } diff --git a/app/Http/Middleware/WebApi.php b/app/Http/Middleware/WebApi.php index ce585d68..d6da6437 100644 --- a/app/Http/Middleware/WebApi.php +++ b/app/Http/Middleware/WebApi.php @@ -2,8 +2,6 @@ namespace App\Http\Middleware; -use App\Models\Node; -use App\Models\NodeAuth; use Closure; use Illuminate\Http\JsonResponse; use Response; @@ -20,7 +18,7 @@ class WebApi */ public function handle($request, Closure $next) { - $id = $request->id; + $node = $request->node; $key = $request->header('key'); $time = $request->header('timestamp'); @@ -28,17 +26,8 @@ class WebApi return $this->returnData('Your key is null!'); } - if (! isset($id)) {// 未提供 node - return $this->returnData('Your Node Id is null!'); - } - - $node = Node::find($id); - if (! $node) {// node不存在 - return $this->returnData('Unknown Node!'); - } - - $nodeAuth = NodeAuth::whereNodeId($id)->first(); - if (! $nodeAuth || $key !== $nodeAuth->key) {// key不存在/不匹配 + $nodeAuth = $node->auth ?? null; + if (! isset($nodeAuth) || $key !== $nodeAuth->key) {// key不存在/不匹配 return $this->returnData('Token is invalid!'); } @@ -50,7 +39,7 @@ class WebApi } // 返回数据 - public function returnData($message): JsonResponse + public function returnData(string $message): JsonResponse { return Response::json(['status' => 'fail', 'code' => 404, 'message' => $message]); } diff --git a/app/Jobs/VNet/reloadNode.php b/app/Jobs/VNet/reloadNode.php index d1bdc7aa..592cf48a 100644 --- a/app/Jobs/VNet/reloadNode.php +++ b/app/Jobs/VNet/reloadNode.php @@ -42,10 +42,10 @@ class reloadNode implements ShouldQueue 'push_port' => $node->push_port, 'single' => $node->single, 'secret' => $node->auth->secret, - 'is_udp' => $node->is_udp, 'speed_limit' => $node->getRawOriginal('speed_limit'), + 'is_udp' => $node->is_udp, 'client_limit' => $node->client_limit, - 'redirect_url' => (string) sysConfig('redirect_url'), + // 'redirect_url' => (string) sysConfig('redirect_url'), ]); if (! $ret) { diff --git a/app/Models/Node.php b/app/Models/Node.php index 25995aa1..802843e0 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -26,11 +26,26 @@ class Node extends Model return $this->hasMany(NodeHeartBeat::class); } + public function onlineIps(): HasMany + { + return $this->hasMany(NodeOnlineIp::class); + } + public function onlineLogs(): HasMany { return $this->hasMany(NodeOnlineLog::class); } + public function userDataFlowLogs(): HasMany + { + return $this->hasMany(UserDataFlowLog::class); + } + + public function ruleLogs(): HasMany + { + return $this->hasMany(RuleLog::class); + } + public function pingLogs(): HasMany { return $this->hasMany(NodePing::class); diff --git a/app/Models/NodeHeartBeat.php b/app/Models/NodeHeartBeat.php index b8b405b1..687816cb 100644 --- a/app/Models/NodeHeartBeat.php +++ b/app/Models/NodeHeartBeat.php @@ -11,6 +11,7 @@ class NodeHeartbeat extends Model { public $timestamps = false; protected $table = 'node_heartbeat'; + protected $guarded = []; public function scopeRecently($query) { diff --git a/app/Models/NodeOnlineIp.php b/app/Models/NodeOnlineIp.php index 0e323032..89387e39 100644 --- a/app/Models/NodeOnlineIp.php +++ b/app/Models/NodeOnlineIp.php @@ -12,6 +12,7 @@ class NodeOnlineIp extends Model { public $timestamps = false; protected $table = 'node_online_ip'; + protected $guarded = []; public function node(): BelongsTo { diff --git a/app/Models/NodeOnlineLog.php b/app/Models/NodeOnlineLog.php index 7f895251..69842fd1 100644 --- a/app/Models/NodeOnlineLog.php +++ b/app/Models/NodeOnlineLog.php @@ -11,4 +11,5 @@ class NodeOnlineLog extends Model { public $timestamps = false; protected $table = 'node_online_log'; + protected $guarded = []; } diff --git a/app/Models/RuleLog.php b/app/Models/RuleLog.php index 8a0d092c..3ab919a6 100644 --- a/app/Models/RuleLog.php +++ b/app/Models/RuleLog.php @@ -12,6 +12,7 @@ class RuleLog extends Model { public const UPDATED_AT = null; protected $table = 'rule_log'; + protected $guarded = []; public function user(): BelongsTo { diff --git a/app/Models/UserDataFlowLog.php b/app/Models/UserDataFlowLog.php index 33386fac..9b8dc2c6 100644 --- a/app/Models/UserDataFlowLog.php +++ b/app/Models/UserDataFlowLog.php @@ -12,16 +12,17 @@ class UserDataFlowLog extends Model { public $timestamps = false; protected $table = 'user_traffic_log'; + protected $guarded = []; // 关联账号 public function user(): BelongsTo { - return $this->belongsTo(User::class, 'user_id', 'id'); + return $this->belongsTo(User::class); } // 关联节点 public function node(): BelongsTo { - return $this->belongsTo(Node::class, 'node_id', 'id'); + return $this->belongsTo(Node::class); } } diff --git a/routes/api.php b/routes/api.php index d100132d..4ffdbad5 100644 --- a/routes/api.php +++ b/routes/api.php @@ -5,46 +5,46 @@ Route::group(['namespace' => 'Api\WebApi', 'middleware' => 'webApi'], function ( // VNet后端WEBAPI V1版 Route::group(['prefix' => 'web/v1'], function () { Route::get('node/{node}', 'VNetController@getNodeInfo'); // 获取节点信息 - Route::post('nodeStatus/{id}', 'BaseController@setNodeStatus'); // 上报节点心跳信息 - Route::post('nodeOnline/{id}', 'BaseController@setNodeOnline'); // 上报节点在线人数 + Route::post('nodeStatus/{node}', 'BaseController@setNodeStatus'); // 上报节点心跳信息 + Route::post('nodeOnline/{node}', 'BaseController@setNodeOnline'); // 上报节点在线人数 Route::get('userList/{node}', 'VNetController@getUserList'); // 获取节点可用的用户列表 - Route::post('userTraffic/{id}', 'BaseController@setUserTraffic'); // 上报用户流量日志 + Route::post('userTraffic/{node}', 'BaseController@setUserTraffic'); // 上报用户流量日志 Route::get('nodeRule/{node}', 'BaseController@getNodeRule'); // 获取节点的审计规则 - Route::post('trigger/{id}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录 + Route::post('trigger/{node}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录 }); // VNet后端WEBAPI V2版 Route::group(['prefix' => 'vnet/v2'], function () { Route::get('node/{node}', 'VNetController@getNodeInfo'); // 获取节点信息 - Route::post('nodeStatus/{id}', 'BaseController@setNodeStatus'); // 上报节点心跳信息 - Route::post('nodeOnline/{id}', 'BaseController@setNodeOnline'); // 上报节点在线人数 + Route::post('nodeStatus/{node}', 'BaseController@setNodeStatus'); // 上报节点心跳信息 + Route::post('nodeOnline/{node}', 'BaseController@setNodeOnline'); // 上报节点在线人数 Route::get('userList/{node}', 'VNetController@getUserList'); // 获取节点可用的用户列表 - Route::post('userTraffic/{id}', 'BaseController@setUserTraffic'); // 上报用户流量日志 + Route::post('userTraffic/{node}', 'BaseController@setUserTraffic'); // 上报用户流量日志 Route::get('nodeRule/{node}', 'BaseController@getNodeRule'); // 获取节点的审计规则 - Route::post('trigger/{id}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录 + Route::post('trigger/{node}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录 }); // V2Ray后端WEBAPI V1版 Route::group(['prefix' => 'v2ray/v1'], function () { Route::get('node/{node}', 'V2RayController@getNodeInfo'); // 获取节点信息 - Route::post('nodeStatus/{id}', 'BaseController@setNodeStatus'); // 上报节点心跳信息 - Route::post('nodeOnline/{id}', 'BaseController@setNodeOnline'); // 上报节点在线人数 + Route::post('nodeStatus/{node}', 'BaseController@setNodeStatus'); // 上报节点心跳信息 + Route::post('nodeOnline/{node}', 'BaseController@setNodeOnline'); // 上报节点在线人数 Route::get('userList/{node}', 'V2RayController@getUserList'); // 获取节点可用的用户列表 - Route::post('userTraffic/{id}', 'BaseController@setUserTraffic'); // 上报用户流量日志 + Route::post('userTraffic/{node}', 'BaseController@setUserTraffic'); // 上报用户流量日志 Route::get('nodeRule/{node}', 'BaseController@getNodeRule'); // 获取节点的审计规则 - Route::post('trigger/{id}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录 + Route::post('trigger/{node}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录 Route::post('certificate/{node}', 'V2RayController@addCertificate'); // 上报节点伪装域名证书信息 }); // Trojan后端WEBAPI V1版 Route::group(['prefix' => 'trojan/v1'], function () { Route::get('node/{node}', 'TrojanController@getNodeInfo'); // 获取节点信息 - Route::post('nodeStatus/{id}', 'BaseController@setNodeStatus'); // 上报节点心跳信息 - Route::post('nodeOnline/{id}', 'BaseController@setNodeOnline'); // 上报节点在线人数 + Route::post('nodeStatus/{node}', 'BaseController@setNodeStatus'); // 上报节点心跳信息 + Route::post('nodeOnline/{node}', 'BaseController@setNodeOnline'); // 上报节点在线人数 Route::get('userList/{node}', 'TrojanController@getUserList'); // 获取节点可用的用户列表 - Route::post('userTraffic/{id}', 'BaseController@setUserTraffic'); // 上报用户流量日志 + Route::post('userTraffic/{node}', 'BaseController@setUserTraffic'); // 上报用户流量日志 Route::get('nodeRule/{node}', 'BaseController@getNodeRule'); // 获取节点的审计规则 - Route::post('trigger/{id}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录 + Route::post('trigger/{node}', 'BaseController@addRuleLog'); // 上报用户触发的审计规则记录 }); });