refactor exam progress + filter donate status

This commit is contained in:
xiaomlove
2021-06-11 20:32:57 +08:00
parent 4205978a68
commit 0e8ce200cd
12 changed files with 280 additions and 63 deletions
+10 -2
View File
@@ -60,12 +60,19 @@
<div style="color: #aaa">Unit: days. When assign to user, begin and end are used if they are specified. Otherwise begin time is the time at assignment, and the end time is the time at assignment plus the duration.</div> <div style="color: #aaa">Unit: days. When assign to user, begin and end are used if they are specified. Otherwise begin time is the time at assignment, and the end time is the time at assignment plus the duration.</div>
</el-form-item> </el-form-item>
<el-form-item label="Target User Class" prop="filters.classes"> <el-form-item label="Target user class" prop="filters.classes">
<el-checkbox-group v-model="formData.filters.classes"> <el-checkbox-group v-model="formData.filters.classes">
<el-checkbox v-for="(item, index) in allClasses" :label="index" :key="index">{{item}}</el-checkbox> <el-checkbox v-for="(item, index) in allClasses" :label="index" :key="index">{{item}}</el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
<el-form-item label="Target user donate" prop="filters.donate_status">
<el-checkbox-group v-model="formData.filters.donate_status">
<el-checkbox label="no">No</el-checkbox>
<el-checkbox label="yes">Yes</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="Target User Register Time"> <el-form-item label="Target User Register Time">
<el-date-picker <el-date-picker
v-model="formData.filters.register_time_range" v-model="formData.filters.register_time_range"
@@ -119,7 +126,8 @@ export default {
indexes: [], indexes: [],
filters: { filters: {
classes: [], classes: [],
register_time_range: [] register_time_range: [],
donate_status: []
}, },
status: '', status: '',
is_discovered: '' is_discovered: ''
+6
View File
@@ -57,6 +57,12 @@ class ExamResource extends JsonResource
); );
} }
$filter = Exam::FILTER_USER_DONATE;
if (!empty($currentFilters->{$filter})) {
$donateStatus = $classes = collect(User::$donateStatus)->only($currentFilters->{$filter});
$arr[] = sprintf('%s: %s', Exam::$filters[$filter]['name'], $donateStatus->pluck('text')->implode(', '));
}
return implode("\n", $arr); return implode("\n", $arr);
} }
+2
View File
@@ -45,10 +45,12 @@ class Exam extends NexusModel
const FILTER_USER_CLASS = 'classes'; const FILTER_USER_CLASS = 'classes';
const FILTER_USER_REGISTER_TIME_RANGE = 'register_time_range'; const FILTER_USER_REGISTER_TIME_RANGE = 'register_time_range';
const FILTER_USER_DONATE = 'donate_status';
public static $filters = [ public static $filters = [
self::FILTER_USER_CLASS => ['name' => 'User class'], self::FILTER_USER_CLASS => ['name' => 'User class'],
self::FILTER_USER_REGISTER_TIME_RANGE => ['name' => 'User register time range'], self::FILTER_USER_REGISTER_TIME_RANGE => ['name' => 'User register time range'],
self::FILTER_USER_DONATE => ['name' => 'User donate'],
]; ];
protected static function booted() protected static function booted()
+1 -1
View File
@@ -4,7 +4,7 @@ namespace App\Models;
class ExamProgress extends NexusModel class ExamProgress extends NexusModel
{ {
protected $fillable = ['exam_user_id', 'exam_id', 'uid', 'index', 'value', 'torrent_id']; protected $fillable = ['exam_user_id', 'exam_id', 'uid', 'index', 'init_value', 'value', 'torrent_id'];
public $timestamps = true; public $timestamps = true;
} }
+16
View File
@@ -59,6 +59,14 @@ class User extends Authenticatable
self::CLASS_STAFF_LEADER => ['text' => 'Staff Leader'], self::CLASS_STAFF_LEADER => ['text' => 'Staff Leader'],
]; ];
const DONATE_YES = 'yes';
const DONATE_NO = 'no';
public static $donateStatus = [
self::DONATE_YES => ['text' => 'Yes'],
self::DONATE_NO => ['text' => 'No'],
];
public static $cardTitles = [ public static $cardTitles = [
'uploaded_human' => '上传', 'uploaded_human' => '上传',
'downloaded_human' => '下载', 'downloaded_human' => '下载',
@@ -73,6 +81,14 @@ class User extends Authenticatable
return self::$classes[$this->class]['text'] ?? ''; return self::$classes[$this->class]['text'] ?? '';
} }
public function getDonateStatusAttribute()
{
if (empty($this->donoruntil) || $this->donoruntil == '0000-00-00 00:00:00') {
return self::DONATE_NO;
}
return self::DONATE_YES;
}
/** /**
* 为数组 / JSON 序列化准备日期。 * 为数组 / JSON 序列化准备日期。
+95 -21
View File
@@ -8,6 +8,7 @@ use App\Models\ExamProgress;
use App\Models\ExamUser; use App\Models\ExamUser;
use App\Models\Message; use App\Models\Message;
use App\Models\Setting; use App\Models\Setting;
use App\Models\Snatch;
use App\Models\Torrent; use App\Models\Torrent;
use App\Models\User; use App\Models\User;
use App\Models\UserBanLog; use App\Models\UserBanLog;
@@ -173,22 +174,40 @@ class ExamRepository extends BaseRepository
} }
$logPrefix = sprintf('exam: %s, user: %s', $exam->id, $user->id); $logPrefix = sprintf('exam: %s, user: %s', $exam->id, $user->id);
$filters = $exam->filters; $filters = $exam->filters;
if (empty($filters->classes)) {
$filter = Exam::FILTER_USER_CLASS;
if (empty($filters->{$filter})) {
do_log("$logPrefix, exam: {$exam->id} no class"); do_log("$logPrefix, exam: {$exam->id} no class");
return false; return false;
} }
if (!in_array($user->class, $filters->classes)) { if (!in_array($user->class, $filters->{$filter})) {
do_log("$logPrefix, user class: {$user->class} not in: " . json_encode($filters)); do_log("$logPrefix, user class: {$user->class} not in: " . json_encode($filters->{$filter}));
return false;
}
$filter = Exam::FILTER_USER_DONATE;
if (empty($filters->{$filter})) {
do_log("$logPrefix, exam: {$exam->id} no donate");
return false;
}
if (!in_array($user->donate_status, $filters->{$filter})) {
do_log("$logPrefix, user donate status: {$user->donate_status} not in: " . json_encode($filters->{$filter}));
return false;
}
$filter = Exam::FILTER_USER_REGISTER_TIME_RANGE;
if (empty($filters->{$filter})) {
do_log("$logPrefix, exam: {$exam->id} no register time range");
return false; return false;
} }
if (!$user->added) { if (!$user->added) {
do_log("$logPrefix, user no added time", 'warning'); do_log("$logPrefix, user no added time", 'warning');
return false; return false;
} }
$added = $user->added->toDateTimeString(); $added = $user->added->toDateTimeString();
$registerTimeBegin = isset($filters->register_time_range[0]) ? Carbon::parse($filters->register_time_range[0])->toDateTimeString() : ''; $registerTimeBegin = isset($filters->{$filter}[0]) ? Carbon::parse($filters->{$filter}[0])->toDateTimeString() : '';
$registerTimeEnd = isset($filters->register_time_range[1]) ? Carbon::parse($filters->register_time_range[1])->toDateTimeString() : ''; $registerTimeEnd = isset($filters->{$filter}[1]) ? Carbon::parse($filters->{$filter}[1])->toDateTimeString() : '';
if (empty($registerTimeBegin)) { if (empty($registerTimeBegin)) {
do_log("$logPrefix, exam: {$exam->id} no register_time_begin"); do_log("$logPrefix, exam: {$exam->id} no register_time_begin");
return false; return false;
@@ -244,7 +263,7 @@ class ExamRepository extends BaseRepository
} }
do_log("$logPrefix, data: " . nexus_json_encode($data)); do_log("$logPrefix, data: " . nexus_json_encode($data));
$examUser = $user->exams()->create($data); $examUser = $user->exams()->create($data);
$this->initProgress($examUser, $user); $this->updateProgress($examUser, $user);
return $examUser; return $examUser;
} }
@@ -355,7 +374,7 @@ class ExamRepository extends BaseRepository
return true; return true;
} }
public function initProgress($examUser, $user = null) public function updateProgress($examUser, $user = null)
{ {
if (!$examUser instanceof ExamUser) { if (!$examUser instanceof ExamUser) {
$examUser = ExamUser::query()->findOrFail((int)$examUser); $examUser = ExamUser::query()->findOrFail((int)$examUser);
@@ -364,29 +383,82 @@ class ExamRepository extends BaseRepository
if (!$user instanceof User) { if (!$user instanceof User) {
$user = $examUser->user()->select(['id', 'uploaded', 'downloaded', 'seedtime', 'leechtime', 'seedbonus'])->first(); $user = $examUser->user()->select(['id', 'uploaded', 'downloaded', 'seedtime', 'leechtime', 'seedbonus'])->first();
} }
$insert = [ $attributes = [
'exam_user_id' => $examUser->id,
'uid' => $user->id, 'uid' => $user->id,
'exam_id' => $exam->id, 'exam_id' => $exam->id,
]; ];
$logPrefix = json_encode($attributes);
$begin = $examUser->begin;
if (empty($begin)) {
throw new \InvalidArgumentException("$logPrefix, exam: {$examUser->id} no begin.");
}
$end = $examUser->end;
if (empty($end)) {
throw new \InvalidArgumentException("$logPrefix, exam: {$examUser->id} no end.");
}
$currentProgrss = [];
foreach ($exam->indexes as $key => $index) { foreach ($exam->indexes as $key => $index) {
if (!isset($index['checked']) || !$index['checked']) { if (!isset($index['checked']) || !$index['checked']) {
continue; continue;
} }
$insert['index'] = $index['index']; $attributes['index'] = $index['index'];
if ($index['index'] == Exam::INDEX_UPLOADED) { if ($index['index'] == Exam::INDEX_UPLOADED) {
$insert['value'] = $user->uploaded; $attributes['value'] = $user->uploaded;
} elseif ($index['index'] == Exam::INDEX_DOWNLOADED) { } elseif ($index['index'] == Exam::INDEX_DOWNLOADED) {
$insert['value'] = $user->downloaded; $attributes['value'] = $user->downloaded;
} elseif ($index['index'] == Exam::INDEX_SEED_BONUS) { } elseif ($index['index'] == Exam::INDEX_SEED_BONUS) {
$insert['value'] = $user->seedbonus; $attributes['value'] = $user->seedbonus;
} elseif ($index['index'] == Exam::INDEX_SEED_TIME_AVERAGE) { } elseif ($index['index'] == Exam::INDEX_SEED_TIME_AVERAGE) {
$insert['value'] = 0; $torrentCountsRes = Snatch::query()
->where('userid', $user->id)
->where('completedat', '>=', $begin)
->where('completedat', '<=', $end)
->selectRaw("count(distinct(torrentid)) as torrent_counts")
->first();
do_log("$logPrefix, torrentCountsRes: " . json_encode($torrentCountsRes));
if ($torrentCountsRes && $torrentCountsRes->torrent_counts > 0) {
$value = $user->seedtime / $torrentCountsRes->torrent_counts;
} else {
$value = 0;
}
$attributes['value'] = $value;
} else { } else {
throw new \RuntimeException("Unknown index: {$index['index']}"); $msg = "Unknown index: {$index['index']}";
do_log("$logPrefix, $msg", 'error');
throw new \RuntimeException($msg);
} }
ExamIndexInitValue::query()->insert($insert); //at the begining, value = init_value, and then value increase.
do_log("insert: " . json_encode($insert)); $progress = ExamProgress::query()
->where('exam_user_id', $examUser->id)
->where('torrent_id', -1)
->where('index', $index['index'])
->orderBy('id', 'desc')
->first();
if ($progress) {
//do update
$progress->update(['value' => $attributes['value']]);
do_log("$logPrefix, doUpdat: " . last_query());
} else {
//do insert
$attributes['init_value'] = $attributes['value'];
$attributes['torrent_id'] = -1;
ExamProgress::query()->insert($attributes);
do_log("$logPrefix, doInsert with: " . json_encode($attributes));
}
$currentProgrss[$index['index']] = $attributes['value'];
} }
$examProgressFormatted = $this->getProgressFormatted($exam, $currentProgrss);
$examNotPassed = array_filter($examProgressFormatted, function ($item) {
return !$item['passed'];
});
$update = [
'progress' => $currentProgrss,
'is_done' => count($examNotPassed) ? ExamUser::IS_DONE_NO : ExamUser::IS_DONE_YES,
];
do_log("[UPDATE_PROGRESS] " . nexus_json_encode($update));
$examUser->update($update);
return true; return true;
} }
@@ -410,14 +482,15 @@ class ExamRepository extends BaseRepository
} }
$examUser = $examUsers->first(); $examUser = $examUsers->first();
$exam = $examUser->exam; $exam = $examUser->exam;
$progress = $this->calculateProgress($examUser); // $progress = $this->calculateProgress($examUser);
$progress = $examUser->progress;
do_log("$logPrefix, progress: " . nexus_json_encode($progress)); do_log("$logPrefix, progress: " . nexus_json_encode($progress));
$examUser->progress = $progress; $examUser->progress = $progress;
$examUser->progress_formatted = $this->getProgressFormatted($exam, $progress); $examUser->progress_formatted = $this->getProgressFormatted($exam, (array)$progress);
return $examUser; return $examUser;
} }
private function calculateProgress(ExamUser $examUser) public function calculateProgress(ExamUser $examUser)
{ {
$logPrefix = "examUser: " . $examUser->id; $logPrefix = "examUser: " . $examUser->id;
$begin = $examUser->begin; $begin = $examUser->begin;
@@ -444,6 +517,7 @@ class ExamRepository extends BaseRepository
if (isset($progressSum[$index])) { if (isset($progressSum[$index])) {
$torrentCount = $examUser->progresses() $torrentCount = $examUser->progresses()
->where('index', $index) ->where('index', $index)
->where('torrent_id', '>=', 0)
->selectRaw('count(distinct(torrent_id)) as torrent_count') ->selectRaw('count(distinct(torrent_id)) as torrent_count')
->first() ->first()
->torrent_count; ->torrent_count;
@@ -587,7 +661,7 @@ class ExamRepository extends BaseRepository
]; ];
do_log("$currentLogPrefix, exam will be assigned to this user."); do_log("$currentLogPrefix, exam will be assigned to this user.");
$examUser = ExamUser::query()->create($insert); $examUser = ExamUser::query()->create($insert);
$this->initProgress($examUser, $user); $this->updateProgress($examUser, $user);
$result++; $result++;
} }
} }
@@ -1,37 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateExamIndexInitValuesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('exam_index_init_values', function (Blueprint $table) {
$table->id();
$table->integer('uid')->index();
$table->integer('exam_user_id');
$table->integer('exam_id')->index();
$table->integer('index')->index();
$table->bigInteger('value');
$table->timestamps();
$table->unique(['exam_user_id', 'exam_id', 'index']);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('exam_index_init_values');
}
}
@@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddInitValueToExamProgressTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('exam_progress', function (Blueprint $table) {
if (!Schema::hasColumn('exam_progress', 'init_value')) {
$table->bigInteger('init_value')->default(0)->after('index');
}
//@todo open in beta10
// $table->unique(['exam_user_id', 'index']);
// $table->dropColumn('torrent_id');
// $table->dropIndex('exam_progress_exam_user_id_index');
// $table->dropIndex('exam_progress_created_at_index');
});
// \Illuminate\Support\Facades\DB::statement('alter table exam_progress modify created_at timestamp default current_timestamp');
// \Illuminate\Support\Facades\DB::statement('alter table exam_progress modify updated_at timestamp default current_timestamp on update current_timestamp');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('exam_progress', function (Blueprint $table) {
$table->dropColumn('init_value');
// $table->dropUnique('exam_progress_exam_user_id_index_unique');
// $table->integer('torrent_id')->after('uid');
// $table->index('exam_user_id');
// $table->index('created_at');
});
}
}
@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddCompletedatIndexToSnatchedTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('snatched', function (Blueprint $table) {
$table->index('completedat');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('snatched', function (Blueprint $table) {
$table->dropIndex('snatched_completedat_index');
});
}
}
+1 -1
View File
@@ -430,7 +430,7 @@ $lang_settings = array
'row_torrents_category_mode' => "种子区分类模式", 'row_torrents_category_mode' => "种子区分类模式",
'text_torrents_category_mode_note' => "改变种子区的分类模式。", 'text_torrents_category_mode_note' => "改变种子区的分类模式。",
'row_special_category_mode' => "特别区分类模式", 'row_special_category_mode' => "特别区分类模式",
'text_special_category_mode_note' => "改变特区的分类模式。", 'text_special_category_mode_note' => "改变特区的分类模式。",
'row_default_site_language' => "默认站点语言", 'row_default_site_language' => "默认站点语言",
'text_default_site_language_note' => "改变登录页面的默认语言。", 'text_default_site_language_note' => "改变登录页面的默认语言。",
'row_default_stylesheet' => "默认界面风格", 'row_default_stylesheet' => "默认界面风格",
+1 -1
View File
@@ -431,7 +431,7 @@ $lang_settings = array
'row_torrents_category_mode' => "種子區分類型態", 'row_torrents_category_mode' => "種子區分類型態",
'text_torrents_category_mode_note' => "改變種子區的分類型態。", 'text_torrents_category_mode_note' => "改變種子區的分類型態。",
'row_special_category_mode' => "特別區分類型態", 'row_special_category_mode' => "特別區分類型態",
'text_special_category_mode_note' => "改變特區的分類型態。", 'text_special_category_mode_note' => "改變特區的分類型態。",
'row_default_site_language' => "預設網站語言", 'row_default_site_language' => "預設網站語言",
'text_default_site_language_note' => "改變登入頁面的預設語言。", 'text_default_site_language_note' => "改變登入頁面的預設語言。",
'row_default_stylesheet' => "預設介面風格", 'row_default_stylesheet' => "預設介面風格",
+71
View File
@@ -3,8 +3,11 @@
namespace Nexus\Install; namespace Nexus\Install;
use App\Models\Category; use App\Models\Category;
use App\Models\Exam;
use App\Models\ExamUser;
use App\Models\Icon; use App\Models\Icon;
use App\Models\Setting; use App\Models\Setting;
use App\Repositories\ExamRepository;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Nexus\Database\NexusDB; use Nexus\Database\NexusDB;
@@ -100,4 +103,72 @@ class Update extends Install
} }
public function migrateExamProgress()
{
if (!NexusDB::schema()->hasColumn('exam_progress', 'init_value')) {
sql_query('alter table exam_progress add column `init_value` bigint(20) NOT NULL after `index`');
$log = 'add column init_value on table exam_progress.';
$this->doLog($log);
} else {
$log = 'column init_value already exists on table exam_progress.';
$this->doLog($log);
}
$examUsersQuery = ExamUser::query()->where('status', ExamUser::STATUS_NORMAL)->with('user');
$page = 1;
$size = 100;
while (true) {
$examUsers = $examUsersQuery->forPage($page, $size)->get();
$log = "fetch exam user by: " . last_query();
$this->doLog($log);
if ($examUsers->isEmpty()) {
$log = "no more exam user to handle...";
$this->doLog($log);
break;
}
$log = 'get init_vlaue...';
$this->doLog($log);
foreach ($examUsers as $examUser) {
$oldProgress = $examUser->progress;
$user = $examUser->user;
$currentLogPrefix = "examUser: " . $examUser->toJson();
$log = sprintf("$currentLogPrefix, progress: %s", json_encode($oldProgress));
$this->doLog($log);
foreach ($oldProgress as $index => $progressValue) {
if ($index == Exam::INDEX_DOWNLOADED) {
$value = $user->downloaded;
$initValue = $value - $progressValue;
} elseif ($index == Exam::INDEX_UPLOADED) {
$value = $user->uploaded;
$initValue = $value - $progressValue;
} elseif ($index == Exam::INDEX_SEED_BONUS) {
$value = $user->seedbonus;
$initValue = $value - $progressValue;
} elseif ($index == Exam::INDEX_SEED_TIME_AVERAGE) {
$value = $progressValue;
$initValue = 0;
} else {
$log = sprintf("$currentLogPrefix, invalid index: %s, skip!", $index);
$this->doLog($log);
continue;
}
$insert = [
'exam_user_id' => $examUser->id,
'exam_id' => $examUser->exam_id,
'uid' => $examUser->uid,
'index' => $index,
'torrent_id' => -1,
'value' => $value,
'init_value' => $initValue,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
];
NexusDB::table('exam_progress')->insert($insert);
$log = "$currentLogPrefix, insert index: $index progress: " . json_encode($insert);
$this->doLog($log);
}
}
$page++;
}
}
} }