From ea176b1615ea7b22f4916e38551735ad23c8a30c Mon Sep 17 00:00:00 2001
From: xiaomlove <1939737565@qq.com>
Date: Thu, 29 Jan 2026 20:24:36 +0700
Subject: [PATCH] bonus logs
---
app/Enums/Permission/PermissionEnum.php | 1 +
.../Resources/User/BonusLogResource.php | 2 +-
app/Models/BonusLogs.php | 29 ++++-
app/Repositories/BonusRepository.php | 47 ++++++++
config/clickhouse.php | 14 +--
include/globalfunctions.php | 1 +
nexus/Database/ClickHouse.php | 54 +++++++++
public/bonus-log.php | 110 ++++++++++++++++++
public/claim.php | 2 +-
public/userdetails.php | 5 +-
resources/lang/en/bonus-log.php | 5 +
resources/lang/zh_CN/bonus-log.php | 5 +
resources/lang/zh_TW/bonus-log.php | 5 +
13 files changed, 267 insertions(+), 13 deletions(-)
create mode 100644 nexus/Database/ClickHouse.php
create mode 100644 public/bonus-log.php
diff --git a/app/Enums/Permission/PermissionEnum.php b/app/Enums/Permission/PermissionEnum.php
index 342815fb..07647c32 100644
--- a/app/Enums/Permission/PermissionEnum.php
+++ b/app/Enums/Permission/PermissionEnum.php
@@ -16,5 +16,6 @@ enum PermissionEnum: string {
case MANAGE_USER_BASIC_INFO = "prfmanage";
case MANAGE_USER_CONFIDENTIAL_INFO = "cruprfmanage";
case VIEW_USER_CONFIDENTIAL_INFO = "userprofile";
+ case VIEW_USER_HISTORY = "viewhistory";
}
diff --git a/app/Filament/Resources/User/BonusLogResource.php b/app/Filament/Resources/User/BonusLogResource.php
index 9a58bc98..35cab68d 100644
--- a/app/Filament/Resources/User/BonusLogResource.php
+++ b/app/Filament/Resources/User/BonusLogResource.php
@@ -92,7 +92,7 @@ class BonusLogResource extends Resource
})
,
SelectFilter::make('business_type')
- ->options(BonusLogs::listStaticProps(Arr::except(BonusLogs::$businessTypes, BonusLogs::$businessTypeBonus), 'bonus-log.business_types', true))
+ ->options(BonusLogs::listBusinessTypeOptions(BonusLogs::CATEGORY_COMMON))
->label(__('bonus-log.fields.business_type'))
->searchable(true)
,
diff --git a/app/Models/BonusLogs.php b/app/Models/BonusLogs.php
index d7e916cb..17d55355 100644
--- a/app/Models/BonusLogs.php
+++ b/app/Models/BonusLogs.php
@@ -4,14 +4,17 @@ namespace App\Models;
use Carbon\Carbon;
+use Illuminate\Support\Arr;
class BonusLogs extends NexusModel
{
protected $table = 'bonus_logs';
- protected $fillable = ['uid', 'business_type', 'old_total_value', 'value', 'new_total_value', 'comment'];
+ protected $fillable = ['uid', 'business_type', 'old_total_value', 'value', 'new_total_value', 'comment', 'created_at', 'updated_at'];
public $timestamps = true;
+ const CATEGORY_COMMON = 'common';
+ const CATEGORY_SEEDING = 'seeding';
const DEFAULT_BONUS_CANCEL_ONE_HIT_AND_RUN = 10000;
const DEFAULT_BONUS_BUY_ATTENDANCE_CARD = 1000;
@@ -100,7 +103,7 @@ class BonusLogs extends NexusModel
self::BUSINESS_TYPE_SEEDING_MEDAL_ADDITION => ['text' => 'Seeding medal addition'],
];
- public static array $businessTypeBonus = [
+ public static array $businessTypeSeeding = [
self::BUSINESS_TYPE_SEEDING_BASIC,
self::BUSINESS_TYPE_SEEDING_DONOR_ADDITION,
self::BUSINESS_TYPE_SEEDING_OFFICIAL_ADDITION,
@@ -108,6 +111,28 @@ class BonusLogs extends NexusModel
self::BUSINESS_TYPE_SEEDING_MEDAL_ADDITION
];
+ public static function listBusinessTypeOptions($category = ''): array
+ {
+ $source = BonusLogs::$businessTypes;
+ if ($category == self::CATEGORY_COMMON) {
+ $source = Arr::except(BonusLogs::$businessTypes, BonusLogs::$businessTypeSeeding);
+ } else if ($category == self::CATEGORY_SEEDING) {
+ $source = Arr::only(BonusLogs::$businessTypes, BonusLogs::$businessTypeSeeding);
+ }
+ return self::listStaticProps($source, 'bonus-log.business_types', true);
+ }
+
+ public static function listCategoryOptions(bool $includeSeeding): array
+ {
+ $result = [
+ self::CATEGORY_COMMON => nexus_trans('bonus-log.category_common')
+ ];
+ if ($includeSeeding) {
+ $result[self::CATEGORY_SEEDING] = nexus_trans('bonus-log.category_seeding');
+ }
+ return $result;
+ }
+
public function getBusinessTypeTextAttribute()
{
return nexus_trans('bonus-log.business_types.' . $this->business_type);
diff --git a/app/Repositories/BonusRepository.php b/app/Repositories/BonusRepository.php
index 5be08792..0eeb9cb2 100644
--- a/app/Repositories/BonusRepository.php
+++ b/app/Repositories/BonusRepository.php
@@ -15,6 +15,7 @@ use App\Models\UserMedal;
use App\Models\UserMeta;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
+use Nexus\Database\ClickHouse;
use Nexus\Database\NexusDB;
class BonusRepository extends BaseRepository
@@ -362,5 +363,51 @@ class BonusRepository extends BaseRepository
});
}
+ public function getCount(int $userId, string $category, int $businessType = 0): int
+ {
+ if ($category == BonusLogs::CATEGORY_COMMON) {
+ $query = BonusLogs::query()->where('uid', $userId);
+ if ($businessType > 0) {
+ $query->where('business_type', $businessType);
+ }
+ return $query->count();
+ } else if ($category == BonusLogs::CATEGORY_SEEDING) {
+ $whereStr = "uid = :uid";
+ $binds = ["uid" => $userId];
+ if ($businessType > 0) {
+ $whereStr .= " AND business_type = :business_type";
+ $binds["business_type"] = $businessType;
+ }
+ return ClickHouse::count("bonus_logs", $whereStr, $binds);
+ }
+ throw new \InvalidArgumentException("Invalid category: $category");
+ }
+
+ public function getList(int $userId, string $category, int $businessType = 0, int $page = 1, int $perPage = 50)
+ {
+ if ($category == BonusLogs::CATEGORY_COMMON) {
+ $query = BonusLogs::query()->where('uid', $userId);
+ if ($businessType > 0) {
+ $query->where('business_type', $businessType);
+ }
+ return $query->orderBy("id", "desc")->forPage($page, $perPage)->get();
+ } else if ($category == BonusLogs::CATEGORY_SEEDING) {
+ $sql = "select * from bonus_logs where uid = :uid";
+ $binds = ["uid" => $userId];
+ if ($businessType > 0) {
+ $sql .= " AND business_type = :business_type";
+ $binds["business_type"] = $businessType;
+ }
+ $offset = ($page - 1) * $perPage;
+ $rows = ClickHouse::list("$sql order by created_at desc limit $offset, $perPage", $binds);
+ $result = [];
+ foreach ($rows as $row) {
+ $result[] = new BonusLogs($row);
+ }
+ return $result;
+ }
+ throw new \InvalidArgumentException("Invalid category: $category");
+ }
+
}
diff --git a/config/clickhouse.php b/config/clickhouse.php
index 0ec71cca..78f0c3a6 100644
--- a/config/clickhouse.php
+++ b/config/clickhouse.php
@@ -24,12 +24,12 @@ return [
*/
'connection' => [
- 'host' => env('CLICKHOUSE_HOST', 'localhost'),
- 'port' => env('CLICKHOUSE_HTTP_PORT', 8123),
- 'username' => env('CLICKHOUSE_USER', 'default'),
- 'password' => env('CLICKHOUSE_PASSWORD', ''),
+ 'host' => nexus_env('CLICKHOUSE_HOST', 'localhost'),
+ 'port' => nexus_env('CLICKHOUSE_HTTP_PORT', 8123),
+ 'username' => nexus_env('CLICKHOUSE_USER', 'default'),
+ 'password' => nexus_env('CLICKHOUSE_PASSWORD', ''),
'options' => [
- 'database' => env('CLICKHOUSE_DATABASE', 'default'),
+ 'database' => nexus_env('CLICKHOUSE_DATABASE', 'default'),
'timeout' => 1,
'connectTimeOut' => 2,
],
@@ -42,7 +42,7 @@ return [
*/
'migrations' => [
- 'table' => env('CLICKHOUSE_MIGRATION_TABLE', 'migrations'),
- 'path' => database_path('clickhouse-migrations'),
+ 'table' => nexus_env('CLICKHOUSE_MIGRATION_TABLE', 'migrations'),
+ 'path' => __DIR__ . '/../database/clickhouse-migrations',
],
];
diff --git a/include/globalfunctions.php b/include/globalfunctions.php
index 269a15b8..6add1f01 100644
--- a/include/globalfunctions.php
+++ b/include/globalfunctions.php
@@ -301,6 +301,7 @@ function nexus_config($key, $default = null)
ROOT_PATH . 'config/nexus.php',
ROOT_PATH . 'config/emoji.php',
ROOT_PATH . 'config/captcha.php',
+ ROOT_PATH . 'config/clickhouse.php',
];
foreach ($files as $file) {
$basename = basename($file);
diff --git a/nexus/Database/ClickHouse.php b/nexus/Database/ClickHouse.php
new file mode 100644
index 00000000..32fac00b
--- /dev/null
+++ b/nexus/Database/ClickHouse.php
@@ -0,0 +1,54 @@
+database($options['database']);
+// $client->setTimeout($options['timeout']);
+ $client->setConnectTimeOut($options['connectTimeOut']);
+ self::$client = $client;
+ }
+ return self::$client;
+ }
+
+ public static function count(string $table, string $whereStr = '', array $binds = []): int
+ {
+ $start = microtime(true);
+ $countAlias = "count";
+ $sql = "select count(*) as $countAlias from $table";
+ $whereStr = strtolower(trim($whereStr));
+ if ($whereStr) {
+ if (!str_starts_with($whereStr, 'where')) {
+ $whereStr = "where $whereStr";
+ }
+ $sql .= " $whereStr";
+ }
+ $stat = self::getClient()->select($sql, $binds);
+ $result = $stat->fetchOne($countAlias) ?? 0;
+ $costTime = number_format(microtime(true) - $start, 3);
+ do_log("table: $table, whereStr: $whereStr, binds: " . json_encode($binds) . ", result: $result, cost time: $costTime sec.");
+ return $result;
+ }
+
+ public static function list(string $sql, array $binds = []): array
+ {
+ $start = microtime(true);
+ $stat = self::getClient()->select($sql, $binds);
+ $result = $stat->rows();
+ $costTime = number_format(microtime(true) - $start, 3);
+ do_log("sql: $sql, binds: " . json_encode($binds) . ", result count: " . count($result) . ", cost time: $costTime sec.");
+ return $result;
+ }
+
+
+}
diff --git a/public/bonus-log.php b/public/bonus-log.php
new file mode 100644
index 00000000..d29d946e
--- /dev/null
+++ b/public/bonus-log.php
@@ -0,0 +1,110 @@
+where('id', $uid)->first(\App\Models\User::$commonFields);
+if (!$user) {
+ stderr("Error", "Invalid uid: $uid");
+}
+if ($uid != $CURUSER['id']) {
+ user_can(\App\Enums\Permission\PermissionEnum::VIEW_USER_HISTORY->value, true, $CURUSER['id']);
+}
+$isRecordSeedingBonusLog = \App\Models\Setting::getIsRecordSeedingBonusLog();
+$defaultCategory = \App\Models\BonusLogs::CATEGORY_COMMON;
+$category = $_REQUEST['category'] ?? $defaultCategory;
+$categoryOptions = \App\Models\BonusLogs::listCategoryOptions($isRecordSeedingBonusLog);
+if (!isset($categoryOptions[$category])) {
+ stderr("Error", "Invalid category: $category");
+}
+$businessType = $_REQUEST['business_type'] ?? 0;
+$businessTypeOptions = \App\Models\BonusLogs::listBusinessTypeOptions($isRecordSeedingBonusLog ? '' : $defaultCategory);
+if ($businessType && !isset($businessTypeOptions[$businessType])) {
+ stderr("Error", "Invalid business_type: $businessType");
+}
+
+stdhead(nexus_trans('bonus-log.title_for_user'));
+$pagerParam = "?uid=$uid&category=$category&business_type=$businessType";
+print("
");
+
+$textSelectOnePlease = nexus_trans('nexus.select_one_please');
+$categoryOptionsText = $businessTypeOptionsText = '';
+foreach ($categoryOptions as $name => $text) {
+ $categoryOptionsText .= sprintf(
+ '',
+ $name, isset($_REQUEST['category']) && $_REQUEST['category'] == $name ? ' selected' : '', $text
+ );
+}
+foreach ($businessTypeOptions as $name => $text) {
+ $businessTypeOptionsText .= sprintf(
+ '',
+ $name, isset($_REQUEST['business_type']) && $_REQUEST['business_type'] == $name ? ' selected' : '', $text
+ );
+}
+
+$resetText = nexus_trans('label.reset');
+$submitText = nexus_trans('label.submit');
+$categoryText = nexus_trans('bonus-log.category');
+$businessTypeText = nexus_trans('bonus-log.fields.business_type');
+$filterForm = <<
+
+FORM;
+$resetJs = <<getCount($uid, $category, $businessType);
+list($pagertop, $pagerbottom, $limit, $offset, $pageSize, $page) = pager(50, $total, "$pagerParam&");
+$list = $rep->getList($uid, $category, $businessType, $page + 1, $pageSize);
+begin_main_frame();
+print($filterForm);
+print("");
+print("
+ | ".nexus_trans('bonus-log.fields.business_type')." |
+ ".nexus_trans('bonus-log.fields.old_total_value')." |
+ ".nexus_trans('bonus-log.fields.value')." |
+ ".nexus_trans('bonus-log.fields.new_total_value')." |
+ ".nexus_trans('label.comment')." |
+ ".nexus_trans('label.created_at')." |
+
");
+foreach ($list as $row) {
+ print("
+ | " . $row->businessTypeText . " |
+ " . ($row->old_total_value > 0 ? number_format($row->old_total_value, 1) : '-') . " |
+ " . ($row->old_total_value < $row->new_total_value ? "+" . number_format($row->value, 1) : "-" . number_format($row->value, 1)) . " |
+ " . ($row->new_total_value > 0 ? number_format($row->new_total_value, 1) : '-') . " |
+ " . $row->comment . " |
+ " . $row->created_at . " |
+
");
+}
+
+print("
");
+print($pagerbottom);
+end_main_frame();
+stdfoot();
+
+
diff --git a/public/claim.php b/public/claim.php
index 4ea8e9ad..be8c239f 100644
--- a/public/claim.php
+++ b/public/claim.php
@@ -115,7 +115,7 @@ if ($sort == 'seed_time') {
}
$list = $query->selectRaw("claims.*")->get();
print($filterForm);
-print("");
+print("");
print("
| ".nexus_trans('claim.th_id')." |
".nexus_trans('claim.th_username')." |
diff --git a/public/userdetails.php b/public/userdetails.php
index edf021c8..8ebde2c3 100644
--- a/public/userdetails.php
+++ b/public/userdetails.php
@@ -400,7 +400,8 @@ if ($user["id"] == $CURUSER["id"] || user_can('viewhistory')) {
$states = (new \App\Repositories\ClaimRepository())->getStats($user['id']);
tr_small($lang_functions['menu_claim'], sprintf('%s', $user['id'], $states), 1);
}
- tr_small($lang_userdetails['row_karma_points'], number_format($user['seedbonus'], 1), 1);
+ $bonusLogText = sprintf(' [%s]', $user['id'], nexus_trans("bonus-log.view_detail"));
+ tr_small($lang_userdetails['row_karma_points'], number_format($user['seedbonus'], 1) . $bonusLogText, 1);
tr_small($lang_functions['text_seed_points'], number_format($user['seed_points'], 1) . " (" . nexus_trans('label.updated_at') . ": " . $user['seed_points_updated_at'] . ")", 1);
}
@@ -511,7 +512,7 @@ if (user_can('prfmanage') && $user["class"] < get_user_class())
tr($lang_userdetails['row_comment'], "", 1);
$bonuscomment = \App\Models\BonusLogs::query()
->where("uid", $user["id"])
- ->whereNotIn("business_type", \App\Models\BonusLogs::$businessTypeBonus)
+ ->whereNotIn("business_type", \App\Models\BonusLogs::$businessTypeSeeding)
->orderBy("id", "desc")
->limit(20)
->get()
diff --git a/resources/lang/en/bonus-log.php b/resources/lang/en/bonus-log.php
index 7b0ff6ce..add8d52c 100644
--- a/resources/lang/en/bonus-log.php
+++ b/resources/lang/en/bonus-log.php
@@ -45,4 +45,9 @@ return [
'new_total_value' => 'Post-trade value',
],
'exclude_seeding_bonus' => 'Exclude seeding bonus',
+ 'title_for_user' => 'User bonus details',
+ 'category' => 'Category',
+ 'category_common' => 'Common',
+ 'category_seeding' => 'Seeding',
+ 'view_detail' => 'Details',
];
diff --git a/resources/lang/zh_CN/bonus-log.php b/resources/lang/zh_CN/bonus-log.php
index e82d3875..783f51b5 100644
--- a/resources/lang/zh_CN/bonus-log.php
+++ b/resources/lang/zh_CN/bonus-log.php
@@ -47,4 +47,9 @@ return [
'new_total_value' => '交易后值',
],
'exclude_seeding_bonus' => '不包含做种魔力',
+ 'title_for_user' => '用户魔力明细',
+ 'category' => '分类',
+ 'category_common' => '普通',
+ 'category_seeding' => '做种',
+ 'view_detail' => '明细',
];
diff --git a/resources/lang/zh_TW/bonus-log.php b/resources/lang/zh_TW/bonus-log.php
index 6c8bd0f4..b6d12bb7 100644
--- a/resources/lang/zh_TW/bonus-log.php
+++ b/resources/lang/zh_TW/bonus-log.php
@@ -45,4 +45,9 @@ return [
'new_total_value' => '交易後值',
],
'exclude_seeding_bonus' => '不包含做種魔力',
+ 'title_for_user' => '用戶魔力明細',
+ 'category' => '分類',
+ 'category_common' => '普通',
+ 'category_seeding' => '做種',
+ 'view_detail' => '明細',
];