diff --git a/app/Console/Commands/Test.php b/app/Console/Commands/Test.php index 72c9f849..bb38166a 100644 --- a/app/Console/Commands/Test.php +++ b/app/Console/Commands/Test.php @@ -45,7 +45,8 @@ class Test extends Command public function handle() { $rep = new ExamRepository(); - $r = $rep->listUserExamProgress(1); +// $r = $rep->assignToUser(3); + echo new A; dd($r); } } diff --git a/app/Http/Controllers/ExamController.php b/app/Http/Controllers/ExamController.php index f01bdaf0..2564aede 100644 --- a/app/Http/Controllers/ExamController.php +++ b/app/Http/Controllers/ExamController.php @@ -8,6 +8,7 @@ use App\Http\Resources\UserResource; use App\Repositories\ExamRepository; use App\Repositories\UserRepository; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Auth; class ExamController extends Controller { @@ -110,4 +111,13 @@ class ExamController extends Controller return $this->success($resource); } + public function progress(Request $request) + { + $result = $this->repository->getUserExamProgress(Auth::id()); + $resource = new ExamUserResource($result); + return $resource; +// dd($resource->response()->getData(true)); + return $this->success($resource); + } + } diff --git a/app/Http/Resources/ExamUserResource.php b/app/Http/Resources/ExamUserResource.php index 5a76a3d7..7a5a72b7 100644 --- a/app/Http/Resources/ExamUserResource.php +++ b/app/Http/Resources/ExamUserResource.php @@ -9,6 +9,8 @@ use Illuminate\Http\Resources\Json\JsonResource; class ExamUserResource extends JsonResource { + public $preserveKeys = true; + /** * Transform the resource into an array. * @@ -21,10 +23,14 @@ class ExamUserResource extends JsonResource 'id' => $this->id, 'status' => $this->status, 'status_text' => $this->statusText, - 'created_at' => $this->created_at->format('Y-m-d H:i:s'), + 'created_at' => formatDatetime($this->created_at), + 'progress' => $this->when($this->progress, $this->progress), + 'begin' => formatDatetime($this->begin), + 'end' => formatDatetime($this->end), 'user' => new UserResource($this->whenLoaded('user')), 'exam' => new ExamResource($this->whenLoaded('exam')), ]; } + } diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php index 6761d45b..27a4932d 100644 --- a/app/Http/Resources/UserResource.php +++ b/app/Http/Resources/UserResource.php @@ -19,7 +19,7 @@ class UserResource extends JsonResource 'email' => $this->email, 'username' => $this->username, 'status' => $this->status, - 'added' => $this->added, + 'added' => formatDatetime($this->added), 'class' => $this->class, 'class_text' => $this->class_text, 'avatar' => $this->avatar, diff --git a/app/Logging/NexusFormatter.php b/app/Logging/NexusFormatter.php new file mode 100644 index 00000000..659adee6 --- /dev/null +++ b/app/Logging/NexusFormatter.php @@ -0,0 +1,22 @@ +getHandlers() as $handler) { + $handler->setFormatter($this->formatter()); + } + } + + protected function formatter() + { + $format = "[%datetime%] [" . REQUEST_ID . "] %channel%.%level_name%: %message% %context% %extra%\n"; + return tap(new LineFormatter($format, null, true, true), function ($formatter) { + $formatter->includeStacktraces(); + }); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index bb4ef304..e6489505 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -115,15 +115,9 @@ class User extends Authenticatable } - public function exams(): \Illuminate\Database\Eloquent\Relations\BelongsToMany - { - return $this->belongsToMany(Exam::class, 'exam_users', 'uid', 'exam_id')->withTimestamps(); - } - - public function examDetails(): \Illuminate\Database\Eloquent\Relations\HasMany + public function exams() { return $this->hasMany(ExamUser::class, 'uid'); } - } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 23094c02..0426947e 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,6 +2,7 @@ namespace App\Providers; +use Illuminate\Support\Facades\DB; use Illuminate\Support\ServiceProvider; use Illuminate\Http\Resources\Json\JsonResource; @@ -25,5 +26,6 @@ class AppServiceProvider extends ServiceProvider public function boot() { // JsonResource::withoutWrapping(); + DB::connection(config('database.default'))->enableQueryLog(); } } diff --git a/app/Repositories/ExamRepository.php b/app/Repositories/ExamRepository.php index f3f6e448..3a1590a4 100644 --- a/app/Repositories/ExamRepository.php +++ b/app/Repositories/ExamRepository.php @@ -9,9 +9,6 @@ use App\Models\Torrent; use App\Models\User; use Carbon\Carbon; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; -use Illuminate\Database\Capsule\Manager as Capsule; class ExamRepository extends BaseRepository { @@ -102,7 +99,7 @@ class ExamRepository extends BaseRepository $logPrefix = "uid: $uid"; $exams = $this->listValid(); if ($exams->isEmpty()) { - Log::info("$logPrefix, no valid exam."); + do_log("$logPrefix, no valid exam."); return $exams; } $user = User::query()->findOrFail($uid, ['id', 'username', 'added', 'class']); @@ -110,11 +107,15 @@ class ExamRepository extends BaseRepository $filtered = $exams->filter(function (Exam $exam) use ($user, $logPrefix) { $filters = $exam->filters; if (empty($filters->classes)) { - Log::info("$logPrefix, exam: {$exam->id} no class"); + do_log("$logPrefix, exam: {$exam->id} no class"); return false; } if (!in_array($user->class, $filters->classes)) { - Log::info("$logPrefix, user class: {$user->class} not in: " . json_encode($filters)); + do_log("$logPrefix, user class: {$user->class} not in: " . json_encode($filters)); + return false; + } + if (!$user->added) { + do_log("$logPrefix, user no added time", 'warning'); return false; } @@ -122,20 +123,20 @@ class ExamRepository extends BaseRepository $registerTimeBegin = $filters->register_time_range[0] ? Carbon::parse($filters->register_time_range[0])->toDateString() : ''; $registerTimeEnd = $filters->register_time_range[1] ? Carbon::parse($filters->register_time_range[1])->toDateString() : ''; if (empty($registerTimeBegin)) { - Log::info("$logPrefix, exam: {$exam->id} no register_time_begin"); + do_log("$logPrefix, exam: {$exam->id} no register_time_begin"); return false; } if ($added < $registerTimeBegin) { - Log::info("$logPrefix, added: $added not after: " . $registerTimeBegin); + do_log("$logPrefix, added: $added not after: " . $registerTimeBegin); return false; } if (empty($registerTimeEnd)) { - Log::info("$logPrefix, exam: {$exam->id} no register_time_end"); + do_log("$logPrefix, exam: {$exam->id} no register_time_end"); return false; } if ($added > $registerTimeEnd) { - Log::info("$logPrefix, added: $added not before: " . $registerTimeEnd); + do_log("$logPrefix, added: $added not before: " . $registerTimeEnd); return false; } @@ -151,15 +152,19 @@ class ExamRepository extends BaseRepository * * @param $uid * @param int $examId + * @param null $begin + * @param null $end * @return mixed */ - public function assignToUser($uid, $examId = 0) + public function assignToUser($uid, $examId = 0, $begin = null, $end = null) { + $logPrefix = "uid: $uid, examId: $examId, begin: $begin, end: $end"; if ($examId > 0) { $exam = Exam::query()->find($examId); } else { $exams = $this->listMatchExam($uid); if ($exams->count() > 1) { + do_log(last_query()); throw new \LogicException("Match exam more than 1."); } $exam = $exams->first(); @@ -170,9 +175,18 @@ class ExamRepository extends BaseRepository $user = User::query()->findOrFail($uid); $exists = $user->exams()->where('exam_id', $exam->id)->exists(); if ($exists) { - throw new \LogicException("exam: {$exam->id} already assign to user: {$user->id}"); + throw new \LogicException("Exam: {$exam->id} already assign to user: {$user->id}"); } - $result = $user->exams()->save($exam); + $data = [ + 'exam_id' => $exam->id, + ]; + if ($begin && $end) { + $logPrefix .= ", specific begin and end"; + $data['begin'] = $begin; + $data['end'] = $end; + } + do_log("$logPrefix, data: " . nexus_json_encode($data)); + $result = $user->exams()->create($data); return $result; } @@ -224,40 +238,59 @@ class ExamRepository extends BaseRepository 'index' => $indexId, 'value' => $value, ]; - Log::info('[addProgress]', $data); + do_log('[addProgress] ' . nexus_json_encode($data)); return $examUser->progresses()->create($data); } - public function listUserExamProgress($uid, $status = null) + public function getUserExamProgress($uid, $status = null) { $logPrefix = "uid: $uid"; $query = ExamUser::query()->with(['exam', 'user'])->where('uid', $uid)->orderBy('exam_id', 'desc'); if ($status) { $query->where('status', $status); } - $result = $query->paginate(); - $idArr = array_column($result->items(), 'id'); - $examTable = (new Exam())->getTable(); - $progressTable = (new ExamProgress())->getTable(); - $progressSum = Capsule::table($progressTable) - ->join($examTable, "$progressTable.exam_id", '=', "$examTable.id") - ->whereIn("$progressTable.exam_user_id", $idArr) - ->whereRaw("$progressTable.created_at >= $examTable.begin and $progressTable.created_at <= $examTable.end") - ->groupByRaw("$progressTable.exam_user_id, $progressTable.`index`") - ->selectRaw("$progressTable.exam_user_id, $progressTable.`index`, sum($progressTable.`value`) as `index_sum`") + $examUsers = $query->get(); + if ($examUsers->isEmpty()) { + return null; + } + if ($examUsers->count() > 1) { + do_log("$logPrefix, user exam more than 1.", 'warning'); + } + /** @var ExamUser $examUser */ + $examUser = $examUsers->first(); + /** @var Exam $exam */ + $exam = $examUser->exam; + $logPrefix .= ", exam: " . $exam->id; + if ($examUser->begin) { + $logPrefix .= ", begin from examUser: " . $examUser->id; + $begin = $examUser->begin; + } elseif ($exam->begin) { + $logPrefix .= ", begin from exam: " . $exam->id; + $begin = $exam->begin; + } else { + do_log("$logPrefix, no begin"); + return null; + } + if ($examUser->end) { + $logPrefix .= ", end from examUser: " . $examUser->id; + $end = $examUser->end; + } elseif ($exam->end) { + $logPrefix .= ", end from exam: " . $exam->id; + $end = $exam->end; + } else { + do_log("$logPrefix, no end"); + return null; + } + $progressSum = $examUser->progresses() + ->where('created_at', '>=', $begin) + ->where('created_at', '<=', $end) + ->selectRaw("`index`, sum(`value`) as sum") + ->groupBy(['index']) ->get(); - $map = []; - foreach ($progressSum as $value) { - $map[$value->exam_user_id][$value->index] = $value->index_sum; - } - - foreach ($result as &$item) { - $item->progress_value = $map[$item->id] ?? []; - } - - return $result; - + do_log("$logPrefix, query: " . last_query() . ", progressSum: " . $progressSum->toJson()); + $examUser->progress = $progressSum->pluck('sum', 'index')->toArray(); + return $examUser; } diff --git a/bootstrap/app.php b/bootstrap/app.php index 6daf55fc..48a3b45e 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -5,6 +5,7 @@ if (!empty($_SERVER['HTTP_X_REQUEST_ID'])) { } else { define('REQUEST_ID', intval(LARAVEL_START * 10000)); } +define('IN_NEXUS', false); /* |-------------------------------------------------------------------------- diff --git a/config/logging.php b/config/logging.php index 91dd4c35..fd559d39 100644 --- a/config/logging.php +++ b/config/logging.php @@ -17,7 +17,7 @@ return [ | */ - 'default' => env('LOG_CHANNEL', 'stack'), + 'default' => env('LOG_CHANNEL', 'daily'), /* |-------------------------------------------------------------------------- @@ -37,6 +37,7 @@ return [ 'channels' => [ 'stack' => [ 'driver' => 'stack', + 'tab' => [\App\Logging\NexusFormatter::class], 'channels' => ['daily'], 'ignore_exceptions' => false, ], @@ -49,8 +50,9 @@ return [ 'daily' => [ 'driver' => 'daily', - 'path' => storage_path('logs/laravel.log'), + 'path' => env('LOG_FILE', '/tmp/nexus.log'), 'level' => env('LOG_LEVEL', 'debug'), + 'tab' => [\App\Logging\NexusFormatter::class], 'days' => 14, ], diff --git a/database/migrations/2021_04_24_084104_create_exam_users_table.php b/database/migrations/2021_04_24_084104_create_exam_users_table.php index a07c1216..9dd0d672 100644 --- a/database/migrations/2021_04_24_084104_create_exam_users_table.php +++ b/database/migrations/2021_04_24_084104_create_exam_users_table.php @@ -18,6 +18,8 @@ class CreateExamUsersTable extends Migration $table->integer('uid')->index(); $table->integer('exam_id')->index(); $table->integer('status')->default(0); + $table->dateTime('begin')->nullable(); + $table->dateTime('end')->nullable(); $table->text('result')->nullable(); $table->timestamps(); }); diff --git a/include/core.php b/include/core.php index ba7e32d7..f59b9705 100644 --- a/include/core.php +++ b/include/core.php @@ -2,6 +2,7 @@ if(!defined('IN_TRACKER')) { die('Hacking attempt!'); } +define('IN_NEXUS', true); define('ROOT_PATH', $rootpath); define('VERSION_NUMBER', '1.6.0'); define('CURRENT_SCRIPT', strstr(basename($_SERVER['SCRIPT_FILENAME']), '.', true)); diff --git a/include/eloquent.php b/include/eloquent.php index 9439d4b1..0bc89154 100644 --- a/include/eloquent.php +++ b/include/eloquent.php @@ -8,8 +8,10 @@ $connectionMysql['driver'] = 'mysql'; $connectionMysql['charset'] = 'utf8mb4'; $connectionMysql['collation'] = 'utf8mb4_unicode_ci'; $capsule = new Capsule; -$capsule->addConnection($connectionMysql, 'default'); +$connectionName = \Nexus\Database\DB::ELOQUENT_CONNECTION_NAME; +$capsule->addConnection($connectionMysql, $connectionName); $capsule->setAsGlobal(); $capsule->bootEloquent(); +$capsule->getConnection($connectionName)->enableQueryLog(); diff --git a/include/globalfunctions.php b/include/globalfunctions.php index ba6320cd..88b9b5a2 100644 --- a/include/globalfunctions.php +++ b/include/globalfunctions.php @@ -145,31 +145,51 @@ function nexus_dd($vars) exit(0); } - +/** + * write log, use in both pure nexus and inside laravel + * + * @param $log + * @param string $level + */ function do_log($log, $level = 'info') { - global $CURUSER; $logFile = getLogFile(); - if (($fd = fopen($logFile, 'a')) !== false) { - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); - $content = sprintf( - "[%s] [%s] [%s] [%s] [%s] %s:%s %s%s%s %s%s", - date('Y-m-d H:i:s'), - $level, - defined('REQUEST_ID') ? REQUEST_ID : '', - $CURUSER['id'] ?? 0, - $CURUSER['passkey'] ?? $_REQUEST['passkey'] ?? '', - $backtrace[0]['file'] ?? '', - $backtrace[0]['line'] ?? '', - $backtrace[1]['class'] ?? '', - $backtrace[1]['type'] ?? '', - $backtrace[1]['function'] ?? '', - $log, - PHP_EOL - ); - fwrite($fd, $content); - fclose($fd); + if (($fd = fopen($logFile, 'a')) === false) { + $fd = fopen(sys_get_temp_dir() . '/nexus.log', 'a'); } + static $uid, $passkey, $env; + if (is_null($uid)) { + if (IN_NEXUS) { + global $CURUSER; + $user = $CURUSER; + $uid = $user['id'] ?? 0; + $passkey = $user['passkey'] ?? $_REQUEST['passkey'] ?? ''; + $env = nexus_env('APP_ENV'); + } else { + $user = \Illuminate\Support\Facades\Auth::user(); + $uid = $user->id ?? 0; + $passkey = $user->passkey ?? $_REQUEST['passkey'] ?? ''; + $env = env('APP_ENV'); + } + } + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $content = sprintf( + "[%s] [%s] [%s] [%s] %s.%s %s:%s %s%s%s %s%s", + date('Y-m-d H:i:s'), + defined('REQUEST_ID') ? REQUEST_ID : '', + $uid, + $passkey, + $env, $level, + $backtrace[0]['file'] ?? '', + $backtrace[0]['line'] ?? '', + $backtrace[1]['class'] ?? '', + $backtrace[1]['type'] ?? '', + $backtrace[1]['function'] ?? '', + $log, + PHP_EOL + ); + fwrite($fd, $content); + fclose($fd); } function getLogFile() @@ -178,15 +198,15 @@ function getLogFile() if (!is_null($logFile)) { return $logFile; } - $config = nexus_config('nexus'); + if (IN_NEXUS) { + $config = nexus_config('nexus'); + } else { + $config = config('nexus'); + } $logFile = sys_get_temp_dir() . '/nexus_' . date('Y-m-d') . '.log'; if (!empty($config['log_file'])) { $logFile = $config['log_file']; } - $validSplit = ['daily', 'monthly']; - if (empty($config['log_split']) || !in_array($config['log_split'], $validSplit)) { - return $logFile; - } $lastDotPos = strrpos($logFile, '.'); if ($lastDotPos !== false) { $prefix = substr($logFile, 0, $lastDotPos); @@ -195,16 +215,7 @@ function getLogFile() $prefix = $logFile; $suffix = ''; } - switch ($config['log_split']) { - case 'daily': - $logFile = sprintf('%s-%s%s', $prefix, date('Y-m-d'), $suffix); - break; - case 'monthly': - $logFile = sprintf('%s-%s%s', $prefix, date('Ym'), $suffix); - break; - default: - break; - } + $logFile = sprintf('%s-%s%s', $prefix, date('Y-m-d'), $suffix); return $logFile; } @@ -497,3 +508,28 @@ function fail(...$args) } return api($ret, $msg, $data); } + +function last_query($all = false) +{ + if (IN_NEXUS) { + $queries = \Illuminate\Database\Capsule\Manager::connection(\Nexus\Database\DB::ELOQUENT_CONNECTION_NAME)->getQueryLog(); + } else { + $queries = \Illuminate\Support\Facades\DB::connection(config('database.default'))->getQueryLog(); + } + if ($all) { + return nexus_json_encode($queries); + } + $query = last($queries); + return nexus_json_encode($query); +} + +function formatDatetime($datetime, $format = 'Y-m-d H:i:s') +{ + if ($datetime instanceof \Carbon\Carbon) { + return $datetime->format($format); + } + if (is_numeric($datetime) && $datetime > strtotime('1970')) { + return date($format, $datetime); + } + return $datetime; +} diff --git a/lang/chs/lang_functions.php b/lang/chs/lang_functions.php index 4c050910..26741592 100644 --- a/lang/chs/lang_functions.php +++ b/lang/chs/lang_functions.php @@ -319,6 +319,11 @@ $lang_functions = array 'exam_name' => '考核名称', 'exam_time_range' => '考核时间', 'exam_index' => '考核指标', + 'exam_require' => '要求', + 'exam_progress_current' => '当前', + 'exam_progress_result' => '结果', + 'exam_progress_result_pass_yes' => '通过!', + 'exam_progress_result_pass_no' => '未通过!', ); ?> diff --git a/nexus/Database/DB.php b/nexus/Database/DB.php index ec2c3105..d4802c1b 100644 --- a/nexus/Database/DB.php +++ b/nexus/Database/DB.php @@ -20,6 +20,8 @@ class DB } + const ELOQUENT_CONNECTION_NAME = 'default'; + public function setDriver(DBInterface $driver) { $this->driver = $driver; diff --git a/nexus/Exam/Exam.php b/nexus/Exam/Exam.php index 3806fe8b..3992aff0 100644 --- a/nexus/Exam/Exam.php +++ b/nexus/Exam/Exam.php @@ -11,33 +11,29 @@ class Exam { global $lang_functions; $examRep = new ExamRepository(); - $userExams = $examRep->listUserExamProgress($uid); - if ($userExams->isEmpty()) { + $userExam = $examRep->getUserExamProgress($uid); + if (empty($userExam)) { return ''; } - $htmlArr = []; - foreach ($userExams as $userExam) { - $exam = $userExam->exam; - $row = []; - $row[] = sprintf('%s:%s', $lang_functions['exam_name'], $exam->name); - $row[] = sprintf('%s:%s ~ %s', $lang_functions['exam_time_range'], $exam->begin, $exam->end); - foreach ($exam->indexes as $key => $index) { - if (isset($index['checked']) && $index['checked']) { - $requireValue = $index['require_value']; - $currentValue = $userExam->progress_value[$index['index']] ?? 0; - $unit = ExamModel::$indexes[$index['index']]['unit'] ?? ''; - $row[] = sprintf( - '%s:%s, Require:%s %s, Current:%s %s, Result:%s', - $lang_functions['exam_index'] . ($key + 1), - ExamModel::$indexes[$index['index']]['name'] ?? '', - $requireValue, $unit, - $currentValue, $unit, - $currentValue >= $requireValue ? 'Done!' : 'Not done!' - ); - } + $exam = $userExam->exam; + $row = []; + $row[] = sprintf('%s:%s', $lang_functions['exam_name'], $exam->name); + $row[] = sprintf('%s:%s ~ %s', $lang_functions['exam_time_range'], $exam->begin, $exam->end); + foreach ($exam->indexes as $key => $index) { + if (isset($index['checked']) && $index['checked']) { + $requireValue = $index['require_value']; + $currentValue = $userExam->progress[$index['index']] ?? 0; + $unit = ExamModel::$indexes[$index['index']]['unit'] ?? ''; + $row[] = sprintf( + '%s:%s, %s:%s %s, %s:%s %s, %s:%s', + $lang_functions['exam_index'] . ($key + 1), ExamModel::$indexes[$index['index']]['name'] ?? '', + $lang_functions['exam_require'], $requireValue, $unit, + $lang_functions['exam_progress_current'], $currentValue, $unit, + $lang_functions['exam_progress_result'], + $currentValue >= $requireValue ? $lang_functions['exam_progress_result_pass_yes'] : $lang_functions['exam_progress_result_pass_no'] + ); } - $htmlArr[] = implode("\n", $row); } - return nl2br(implode("\n\n", $htmlArr)); + return nl2br(implode("\n", $row)); } } diff --git a/routes/api.php b/routes/api.php index c92c4a5a..47e48f44 100644 --- a/routes/api.php +++ b/routes/api.php @@ -23,6 +23,7 @@ Route::group(['middleware' => ['auth:sanctum']], function () { Route::resource('exam', \App\Http\Controllers\ExamController::class); Route::get('exam-users', [\App\Http\Controllers\ExamController::class, 'users']); Route::get('exam-index', [\App\Http\Controllers\ExamController::class, 'indexes']); + Route::get('exam-progress', [\App\Http\Controllers\ExamController::class, 'progress']); Route::get('class', [\App\Http\Controllers\UserController::class, 'classes']); });