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>
</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 v-for="(item, index) in allClasses" :label="index" :key="index">{{item}}</el-checkbox>
</el-checkbox-group>
</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-date-picker
v-model="formData.filters.register_time_range"
@@ -119,7 +126,8 @@ export default {
indexes: [],
filters: {
classes: [],
register_time_range: []
register_time_range: [],
donate_status: []
},
status: '',
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);
}
+2
View File
@@ -45,10 +45,12 @@ class Exam extends NexusModel
const FILTER_USER_CLASS = 'classes';
const FILTER_USER_REGISTER_TIME_RANGE = 'register_time_range';
const FILTER_USER_DONATE = 'donate_status';
public static $filters = [
self::FILTER_USER_CLASS => ['name' => 'User class'],
self::FILTER_USER_REGISTER_TIME_RANGE => ['name' => 'User register time range'],
self::FILTER_USER_DONATE => ['name' => 'User donate'],
];
protected static function booted()
+1 -1
View File
@@ -4,7 +4,7 @@ namespace App\Models;
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;
}
+16
View File
@@ -59,6 +59,14 @@ class User extends Authenticatable
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 = [
'uploaded_human' => '上传',
'downloaded_human' => '下载',
@@ -73,6 +81,14 @@ class User extends Authenticatable
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 序列化准备日期。
+95 -21
View File
@@ -8,6 +8,7 @@ use App\Models\ExamProgress;
use App\Models\ExamUser;
use App\Models\Message;
use App\Models\Setting;
use App\Models\Snatch;
use App\Models\Torrent;
use App\Models\User;
use App\Models\UserBanLog;
@@ -173,22 +174,40 @@ class ExamRepository extends BaseRepository
}
$logPrefix = sprintf('exam: %s, user: %s', $exam->id, $user->id);
$filters = $exam->filters;
if (empty($filters->classes)) {
$filter = Exam::FILTER_USER_CLASS;
if (empty($filters->{$filter})) {
do_log("$logPrefix, exam: {$exam->id} no class");
return false;
}
if (!in_array($user->class, $filters->classes)) {
do_log("$logPrefix, user class: {$user->class} not in: " . json_encode($filters));
if (!in_array($user->class, $filters->{$filter})) {
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;
}
if (!$user->added) {
do_log("$logPrefix, user no added time", 'warning');
return false;
}
$added = $user->added->toDateTimeString();
$registerTimeBegin = isset($filters->register_time_range[0]) ? Carbon::parse($filters->register_time_range[0])->toDateTimeString() : '';
$registerTimeEnd = isset($filters->register_time_range[1]) ? Carbon::parse($filters->register_time_range[1])->toDateTimeString() : '';
$registerTimeBegin = isset($filters->{$filter}[0]) ? Carbon::parse($filters->{$filter}[0])->toDateTimeString() : '';
$registerTimeEnd = isset($filters->{$filter}[1]) ? Carbon::parse($filters->{$filter}[1])->toDateTimeString() : '';
if (empty($registerTimeBegin)) {
do_log("$logPrefix, exam: {$exam->id} no register_time_begin");
return false;
@@ -244,7 +263,7 @@ class ExamRepository extends BaseRepository
}
do_log("$logPrefix, data: " . nexus_json_encode($data));
$examUser = $user->exams()->create($data);
$this->initProgress($examUser, $user);
$this->updateProgress($examUser, $user);
return $examUser;
}
@@ -355,7 +374,7 @@ class ExamRepository extends BaseRepository
return true;
}
public function initProgress($examUser, $user = null)
public function updateProgress($examUser, $user = null)
{
if (!$examUser instanceof ExamUser) {
$examUser = ExamUser::query()->findOrFail((int)$examUser);
@@ -364,29 +383,82 @@ class ExamRepository extends BaseRepository
if (!$user instanceof User) {
$user = $examUser->user()->select(['id', 'uploaded', 'downloaded', 'seedtime', 'leechtime', 'seedbonus'])->first();
}
$insert = [
$attributes = [
'exam_user_id' => $examUser->id,
'uid' => $user->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) {
if (!isset($index['checked']) || !$index['checked']) {
continue;
}
$insert['index'] = $index['index'];
$attributes['index'] = $index['index'];
if ($index['index'] == Exam::INDEX_UPLOADED) {
$insert['value'] = $user->uploaded;
$attributes['value'] = $user->uploaded;
} elseif ($index['index'] == Exam::INDEX_DOWNLOADED) {
$insert['value'] = $user->downloaded;
$attributes['value'] = $user->downloaded;
} elseif ($index['index'] == Exam::INDEX_SEED_BONUS) {
$insert['value'] = $user->seedbonus;
$attributes['value'] = $user->seedbonus;
} 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 {
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);
do_log("insert: " . json_encode($insert));
//at the begining, value = init_value, and then value increase.
$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;
}
@@ -410,14 +482,15 @@ class ExamRepository extends BaseRepository
}
$examUser = $examUsers->first();
$exam = $examUser->exam;
$progress = $this->calculateProgress($examUser);
// $progress = $this->calculateProgress($examUser);
$progress = $examUser->progress;
do_log("$logPrefix, progress: " . nexus_json_encode($progress));
$examUser->progress = $progress;
$examUser->progress_formatted = $this->getProgressFormatted($exam, $progress);
$examUser->progress_formatted = $this->getProgressFormatted($exam, (array)$progress);
return $examUser;
}
private function calculateProgress(ExamUser $examUser)
public function calculateProgress(ExamUser $examUser)
{
$logPrefix = "examUser: " . $examUser->id;
$begin = $examUser->begin;
@@ -444,6 +517,7 @@ class ExamRepository extends BaseRepository
if (isset($progressSum[$index])) {
$torrentCount = $examUser->progresses()
->where('index', $index)
->where('torrent_id', '>=', 0)
->selectRaw('count(distinct(torrent_id)) as torrent_count')
->first()
->torrent_count;
@@ -587,7 +661,7 @@ class ExamRepository extends BaseRepository
];
do_log("$currentLogPrefix, exam will be assigned to this user.");
$examUser = ExamUser::query()->create($insert);
$this->initProgress($examUser, $user);
$this->updateProgress($examUser, $user);
$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' => "种子区分类模式",
'text_torrents_category_mode_note' => "改变种子区的分类模式。",
'row_special_category_mode' => "特别区分类模式",
'text_special_category_mode_note' => "改变特区的分类模式。",
'text_special_category_mode_note' => "改变特区的分类模式。",
'row_default_site_language' => "默认站点语言",
'text_default_site_language_note' => "改变登录页面的默认语言。",
'row_default_stylesheet' => "默认界面风格",
+1 -1
View File
@@ -431,7 +431,7 @@ $lang_settings = array
'row_torrents_category_mode' => "種子區分類型態",
'text_torrents_category_mode_note' => "改變種子區的分類型態。",
'row_special_category_mode' => "特別區分類型態",
'text_special_category_mode_note' => "改變特區的分類型態。",
'text_special_category_mode_note' => "改變特區的分類型態。",
'row_default_site_language' => "預設網站語言",
'text_default_site_language_note' => "改變登入頁面的預設語言。",
'row_default_stylesheet' => "預設介面風格",
+71
View File
@@ -3,8 +3,11 @@
namespace Nexus\Install;
use App\Models\Category;
use App\Models\Exam;
use App\Models\ExamUser;
use App\Models\Icon;
use App\Models\Setting;
use App\Repositories\ExamRepository;
use Illuminate\Support\Str;
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++;
}
}
}