mirror of
https://github.com/lkddi/nexusphp.git
synced 2026-04-03 14:10:57 +08:00
341 lines
13 KiB
PHP
341 lines
13 KiB
PHP
<?php
|
|
namespace App\Repositories;
|
|
|
|
use App\Models\Attendance;
|
|
use App\Models\AttendanceLog;
|
|
use App\Models\Setting;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Nexus\Database\NexusDB;
|
|
|
|
class AttendanceRepository extends BaseRepository
|
|
{
|
|
public function attend($uid)
|
|
{
|
|
$attendance = $this->getAttendance($uid);
|
|
$now = Carbon::now();
|
|
$today = Carbon::today();
|
|
$settings = Setting::get('bonus');
|
|
$initialBonus = $settings['attendance_initial'] ?? Attendance::INITIAL_BONUS;
|
|
$isUpdated = 1;
|
|
$initialData = [
|
|
'uid' => $uid,
|
|
'added' => $now,
|
|
'points' => $initialBonus,
|
|
'days' => 1,
|
|
'total_days' => 1,
|
|
];
|
|
$update = $initialData;
|
|
if (!$attendance) {
|
|
//first time
|
|
do_log("[DO_INSERT]: " . nexus_json_encode($initialData));
|
|
$attendance = Attendance::query()->create($initialData);
|
|
} else {
|
|
$added = $attendance->added->startOfDay();
|
|
do_log("[ORIGINAL_DATA]: " . $attendance->toJson());
|
|
if ($added->gte($today)) {
|
|
//already attended today, do nothing
|
|
$isUpdated = 0;
|
|
} else {
|
|
$diffDays = $today->diffInDays($added, true);
|
|
if ($diffDays == 1) {
|
|
//yesterday do it, it's continuous
|
|
$continuousDays = $this->getContinuousDays($attendance, Carbon::yesterday());
|
|
$points = $this->getContinuousPoints($continuousDays + 1);
|
|
do_log("[CONTINUOUS] continuous days from yesterday: $continuousDays, points: $points");
|
|
$update = [
|
|
'added' => $now,
|
|
'points' => $points,
|
|
'days' => $continuousDays + 1,
|
|
'total_days' => $attendance->total_days + 1,
|
|
];
|
|
} else {
|
|
//not continuous
|
|
do_log("[NOT_CONTINUOUS]");
|
|
$update['total_days'] = $attendance->total_days + 1;
|
|
}
|
|
do_log("[DO_UPDATE]: " . nexus_json_encode($update));
|
|
$attendance->update($update);
|
|
}
|
|
}
|
|
if ($isUpdated) {
|
|
User::query()->where('id', $uid)->increment('seedbonus', $update['points']);
|
|
$attendanceLog = [
|
|
'uid' => $attendance->uid,
|
|
'points' => $update['points'],
|
|
'date' => $now->format('Y-m-d'),
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
];
|
|
AttendanceLog::query()->insert($attendanceLog);
|
|
}
|
|
$attendance->added_time = $now->toTimeString();
|
|
$attendance->is_updated = $isUpdated;
|
|
$baseQuery = AttendanceLog::query()->where('date', $today->format('Y-m-d'));
|
|
$attendance->today_counts = (clone $baseQuery)->count();
|
|
$myId = (clone $baseQuery)->where('uid', $uid)->first(['id'])->id;
|
|
$attendance->my_ranking = (clone $baseQuery)->where('id', '<=', $myId)->count();
|
|
do_log("[FINAL_ATTENDANCE]: " . $attendance->toJson());
|
|
return $attendance;
|
|
|
|
}
|
|
|
|
public function getAttendance($uid, $date = '')
|
|
{
|
|
$query = Attendance::query()
|
|
->where('uid', $uid)
|
|
->orderBy('id', 'desc');
|
|
if (!empty($date)) {
|
|
$query->where('added', '>=', Carbon::parse($date)->startOfDay())
|
|
->where('added', '<=', Carbon::parse($date)->endOfDay());
|
|
}
|
|
return $query->first();
|
|
}
|
|
|
|
public function getContinuousPoints($days)
|
|
{
|
|
$settings = Setting::get('bonus');
|
|
$initial = $settings['attendance_initial'] ?? Attendance::INITIAL_BONUS;
|
|
$step = $settings['attendance_step'] ?? Attendance::STEP_BONUS;
|
|
$max = $settings['attendance_max'] ?? Attendance::MAX_BONUS;
|
|
$extraAwards = $settings['attendance_continuous'] ?? Attendance::CONTINUOUS_BONUS;
|
|
$points = min($initial + ($days - 1) * $step, $max);
|
|
krsort($extraAwards);
|
|
foreach ($extraAwards as $key => $value) {
|
|
if ($days == $key) {
|
|
$points += $value;
|
|
break;
|
|
}
|
|
}
|
|
return $points;
|
|
}
|
|
|
|
/**
|
|
* 将旧的 1 人 1 天 1 条迁移到新版 1 人一条
|
|
*
|
|
* @return int
|
|
*/
|
|
public function migrateAttendance(): int
|
|
{
|
|
$page = 1;
|
|
$size = 10000;
|
|
$caseWhens = [];
|
|
$idArr = [];
|
|
$table = 'attendance';
|
|
while (true) {
|
|
$logPrefix = "[MIGRATE_ATTENDANCE], page: $page, size: $size";
|
|
//as soon as possible, don't use eloquent
|
|
$result = NexusDB::table($table)
|
|
->groupBy(['uid'])
|
|
->selectRaw('uid, max(id) as id, count(*) as counts')
|
|
->forPage($page, $size)
|
|
->get();
|
|
do_log("$logPrefix, " . last_query() . ", count: " . $result->count());
|
|
if ($result->isEmpty()) {
|
|
do_log("$logPrefix, no more data...");
|
|
break;
|
|
}
|
|
foreach ($result as $row) {
|
|
$caseWhens[] = sprintf('when %s then %s', $row->id, $row->counts);
|
|
$idArr[] = $row->id;
|
|
do_log(sprintf(
|
|
"$logPrefix, update user: %s(ID: %s) => %s",
|
|
$row->uid, $row->id, $row->counts
|
|
));
|
|
}
|
|
$page++;
|
|
}
|
|
if (empty($caseWhens)) {
|
|
do_log("no data to update...");
|
|
return 0;
|
|
}
|
|
$caseWhenStr = sprintf('case id %s end', implode(' ', $caseWhens));
|
|
$result = NexusDB::table($table)
|
|
->whereIn('id', $idArr)
|
|
->update(['total_days' => NexusDB::raw($caseWhenStr)]);
|
|
|
|
do_log("[MIGRATE_ATTENDANCE] DONE! $caseWhenStr, result: " . var_export($result, true));
|
|
|
|
return count($idArr);
|
|
}
|
|
|
|
/**
|
|
* 清理签到记录,每人只保留一条
|
|
*
|
|
* @return int
|
|
*/
|
|
public function cleanup(): int
|
|
{
|
|
$query = Attendance::query()->groupBy('uid')->havingRaw("count(*) > 1")->selectRaw('uid, max(id) as max_id');
|
|
$page = 1;
|
|
$size = 10000;
|
|
$deleteCounts = 0;
|
|
while (true) {
|
|
$rows = $query->forPage($page, $size)->get();
|
|
$log = "sql: " . last_query() . ", count: " . $rows->count();
|
|
do_log($log, 'info', isRunningInConsole());
|
|
if ($rows->isEmpty()) {
|
|
$log = "no more data....";
|
|
do_log($log, 'info', isRunningInConsole());
|
|
break;
|
|
}
|
|
foreach ($rows as $row) {
|
|
do {
|
|
$deleted = Attendance::query()
|
|
->where('uid', $row->uid)
|
|
->where('id', '<', $row->max_id)
|
|
->limit(10000)
|
|
->delete();
|
|
$log = "delete: $deleted by sql: " . last_query();
|
|
$deleteCounts += $deleted;
|
|
do_log($log, 'info', isRunningInConsole());
|
|
} while ($deleted > 0);
|
|
}
|
|
$page++;
|
|
}
|
|
return $deleteCounts;
|
|
}
|
|
|
|
/**
|
|
* 为 1.7 新的补签功能回写当前连续签到记录
|
|
*
|
|
* @param int $uid
|
|
* @return int
|
|
*/
|
|
public function migrateAttendanceLogs($uid = 0): int
|
|
{
|
|
$cleanUpCounts = $this->cleanup();
|
|
do_log("cleanup count: $cleanUpCounts", 'info', isRunningInConsole());
|
|
|
|
$page = 1;
|
|
$size = 10000;
|
|
$insert = [];
|
|
$table = 'attendance_logs';
|
|
while (true) {
|
|
$logPrefix = "[MIGRATE_ATTENDANCE_LOGS], page: $page, size: $size";
|
|
$query = Attendance::query()
|
|
->where('added', '>=', Carbon::yesterday())
|
|
->forPage($page, $size);
|
|
if ($uid) {
|
|
$query->where('uid', $uid);
|
|
}
|
|
$result = $query->get();
|
|
do_log("$logPrefix, " . last_query() . ", count: " . $result->count(), 'info', isRunningInConsole());
|
|
if ($result->isEmpty()) {
|
|
do_log("$logPrefix, no more data...");
|
|
break;
|
|
}
|
|
foreach ($result as $row) {
|
|
$interval =\DateInterval::createFromDateString("-1 day");
|
|
$period = new \DatePeriod($row->added->addDay(1), $interval, $row->days, \DatePeriod::EXCLUDE_START_DATE);
|
|
$i = 0;
|
|
foreach ($period as $periodValue) {
|
|
$insert[] = sprintf(
|
|
"(%d, %d, '%s')",
|
|
$row->uid, $i == 0 ? $row->points : 0, $periodValue->format('Y-m-d')
|
|
);
|
|
$i++;
|
|
}
|
|
}
|
|
$page++;
|
|
}
|
|
if (empty($insert)) {
|
|
do_log("no data to insert...", 'info', isRunningInConsole());
|
|
return 0;
|
|
}
|
|
$sql = sprintf(
|
|
"insert into `%s` (`uid`, `points`, `date`) values %s on duplicate key update `uid` = values(`uid`)",
|
|
$table, implode(',', $insert)
|
|
);
|
|
NexusDB::statement($sql);
|
|
$insertCount = count($insert);
|
|
do_log("[MIGRATE_ATTENDANCE_LOGS] DONE! insert sql: " . $sql, 'info', isRunningInConsole());
|
|
|
|
return $insertCount;
|
|
}
|
|
|
|
public function getContinuousDays(Attendance $attendance, $start): int
|
|
{
|
|
$start = Carbon::parse($start);
|
|
$logQuery = $attendance->logs()->where('date', '<=', $start->format('Y-m-d'))->orderBy('date', 'desc');
|
|
$attendanceLogs = $logQuery->get(['date'])->keyBy('date');
|
|
$counts = $attendanceLogs->count();
|
|
do_log(sprintf('user: %s, log counts: %s from query: %s', $attendance->uid, $counts, last_query()));
|
|
if ($counts == 0) {
|
|
return 0;
|
|
}
|
|
$interval =\DateInterval::createFromDateString("-1 day");
|
|
$period = new \DatePeriod($start->clone()->addDay(1), $interval, $counts, \DatePeriod::EXCLUDE_START_DATE);
|
|
$days = 0;
|
|
foreach ($period as $value) {
|
|
$checkDate = $value->format('Y-m-d');
|
|
if ($attendanceLogs->has($checkDate)) {
|
|
$days++;
|
|
do_log(sprintf('user: %s, date: %s, [HAS_ATTENDANCE], now days: %s', $attendance->uid, $checkDate, $days));
|
|
} else {
|
|
do_log(sprintf('user: %s, date: %s, [NOT_ATTENDANCE], now days: %s', $attendance->uid, $checkDate, $days));
|
|
break;
|
|
}
|
|
}
|
|
return $days;
|
|
|
|
}
|
|
|
|
public function retroactive($user, $dateStr)
|
|
{
|
|
if (!$user instanceof User) {
|
|
$user = User::query()->findOrFail((int)$user);
|
|
}
|
|
$attendance = $this->getAttendance($user->id);
|
|
if (!$attendance) {
|
|
throw new \LogicException(nexus_trans('attendance.have_not_attendance_yet'));
|
|
}
|
|
$date = Carbon::parse($dateStr);
|
|
$now = Carbon::now();
|
|
if ($date->gte($now) || $now->diffInDays($date, true) > Attendance::MAX_RETROACTIVE_DAYS) {
|
|
throw new \LogicException(nexus_trans('attendance.target_date_can_no_be_retroactive', ['date' => $date->format('Y-m-d')]));
|
|
}
|
|
return NexusDB::transaction(function () use ($user, $attendance, $date) {
|
|
if (AttendanceLog::query()->where('uid', $user->id)->where('date', $date->format('Y-m-d'))->exists()) {
|
|
throw new \RuntimeException(nexus_trans('attendance.already_attendance'));
|
|
}
|
|
if ($user->attendance_card < 1) {
|
|
throw new \RuntimeException(nexus_trans('attendance.card_not_enough'));
|
|
}
|
|
$log = sprintf('user: %s, card: %s, retroactive date: %s', $user->id, $user->attendance_card, $date->format('Y-m-d'));
|
|
$continuousDays = $this->getContinuousDays($attendance, $date->clone()->subDays(1));
|
|
$log .= ", continuousDays from prev day: $continuousDays";
|
|
$points = $this->getContinuousPoints($continuousDays + 1);
|
|
$log .= ", points: $points";
|
|
do_log($log);
|
|
$userUpdates = [
|
|
'attendance_card' => NexusDB::raw('attendance_card - 1'),
|
|
'seedbonus' => NexusDB::raw("seedbonus + $points"),
|
|
];
|
|
$affectedRows = User::query()
|
|
->where('id', $user->id)
|
|
->where('attendance_card', $user->attendance_card)
|
|
->update($userUpdates);
|
|
$msg = "Decrement user attendance_card and increment bonus";
|
|
if ($affectedRows != 1) {
|
|
do_log("$msg fail, query: " . last_query());
|
|
throw new \RuntimeException("$msg fail");
|
|
}
|
|
do_log("$msg success, query: " . last_query());
|
|
$insert = [
|
|
'uid' => $user->id,
|
|
'points' => $points,
|
|
'date' => $date,
|
|
'is_retroactive' => 1,
|
|
];
|
|
$attendanceLog = AttendanceLog::query()->create($insert);
|
|
//Increment total days and update days.
|
|
$attendance->update([
|
|
'total_days' => NexusDB::raw('total_days + 1'),
|
|
'days' => $this->getContinuousDays($attendance, Carbon::today()),
|
|
]);
|
|
return $attendanceLog;
|
|
});
|
|
}
|
|
}
|