Files
im/app/model/BalanceLog.php
T

233 lines
8.2 KiB
PHP
Raw Normal View History

2025-11-07 09:56:20 +08:00
<?php
namespace app\model;
use Symfony\Component\Console\Input\Input;
use think\Model;
use think\facade\Db;
class BalanceLog extends Base
{
// 表结构定义(使用时间戳)
const TABLE_SCHEMA = [
'id' => 'int(11) NOT NULL AUTO_INCREMENT',
'user_id' => 'int(11) NOT NULL',
'currency' => 'varchar(20) NOT NULL',
'amount' => 'decimal(15,2) NOT NULL',
'before' => 'decimal(15,2) NOT NULL',
'after' => 'decimal(15,2) NOT NULL',
'type' => 'varchar(50) NOT NULL',
'created_at' => 'int(11) NOT NULL COMMENT \'UNIX timestamp\'', // 改为整型时间戳
'memo' => 'varchar(255) DEFAULT NULL',
'PRIMARY KEY (`id`)'
];
function getCreatedAtAttr($v){
return $v ? explode('.',$v)[0] : '';
}
protected function getOptions(): array{
return array_merge(parent::getOptions(),[
'connection' => 'mongodb',
// 'append' => [
// 'from_user',
// 'to_user'
// ],
]);
}
public static function create(array|object $data, array $allowField = [], bool $replace = false):\think\model\contract\Modelable
{
$model = new static();
if(isset($data['currency'])){
if(in_array($data['currency'],Config('site.allow_balance_log'))){
$data['status']=isset($data['status']) ? $data['status']:1;
$data['user_id'] = intval($data['user_id']);
$data['amount'] = floatval($data['amount']);
$data['before'] = floatval($data['before']);
$data['after'] = floatval($data['after']);
$data['type'] = $data['type'] instanceof \app\enum\BalanceType ? $data['type']->value : floatval($data['type']);
$model->setSuffix('_'.strtolower($data['currency']))->allowField($allowField)
->replace($replace)
->save($data, true);
}
}
return $model->fetchModel($model);
}
// 创建所有需要的表索引
public static function createAllIndexes(): array
{
$results = [];
$allow_balance_log = Config('site.allow_balance_log');
foreach ($allow_balance_log as $currency) {
$results[$currency] = self::createTableIndexes($currency);
}
return $results;
}
// 创建索引(适配时间戳查询)
public static function createTableIndexes(string $currency): array
{
$table = self::getTableName($currency);
$results = [];
try {
// 确保归档表存在
if (!self::tableExists($table)) {
self::createTableStructure($table);
}
// 主复合索引(使用时间戳)
if (!self::indexExists($table, 'idx_user_currency_type_created')) {
Db::execute("ALTER TABLE `{$table}` ADD INDEX `idx_user_currency_type_created` (`user_id`, `currency`, `type`, `created_at`)");
$results[] = "Created idx_user_currency_type_created on {$table}";
}
// 时间索引(降序优化)
if (!self::indexExists($table, 'idx_created_at')) {
Db::execute("ALTER TABLE `{$table}` ADD INDEX `idx_created_at` (`created_at` DESC)");
$results[] = "Created idx_created_at on {$table}";
}
} catch (\Throwable $e) {
$results['error'] = "Error on {$table}: " . $e->getMessage();
}
return $results;
}
// 检查索引是否存在
protected static function indexExists(string $table, string $indexName): bool
{
$indexes = Db::query("SHOW INDEX FROM `{$table}` WHERE Key_name = ?", [$indexName]);
return !empty($indexes);
}
// 检查表是否存在
protected static function tableExists(string $table): bool
{
try {
Db::query("SELECT 1 FROM `{$table}` LIMIT 1");
return true;
} catch (\Throwable $e) {
return false;
}
}
// 数据归档方法(可在定时任务中调用)
public static function archiveData(int $days = 3): array
{
$results = [];
$allow_balance_log = Config('site.allow_balance_log');
foreach ($allow_balance_log as $currency) {
$results[$currency] = self::archiveCurrencyData($currency, $days);
}
return $results;
}
// 归档指定货币的数据
protected static function archiveCurrencyData(string $currency, int $days): array
{
$table = self::getTableName($currency);
$archiveTable = $table . '_archive';
$cutoffTimestamp = time() - ($days * 86400); // 转为时间戳计算
$result = [
'table' => $table,
'archived' => 0,
'messages' => []
];
try {
// 确保归档表存在
if (!self::tableExists($archiveTable)) {
self::createTableStructure($archiveTable);
$result['messages'][] = "Created archive table: {$archiveTable}";
}
// 分批归档数据
$totalArchived = 0;
Db::table($table)
->where('created_at', '<=', $cutoffTimestamp)
->chunk(1000, function($logs) use ($archiveTable, $table, &$totalArchived) {
Db::table($archiveTable)->insertAll($logs);
$count = count($logs);
Db::table($table)->whereIn('id', array_column($logs, 'id'))->delete();
$totalArchived += $count;
});
$result['archived'] = $totalArchived;
$result['messages'][] = "Archived {$totalArchived} records from {$table}";
// 优化表
Db::execute("OPTIMIZE TABLE `{$table}`");
$result['messages'][] = "Optimized table: {$table}";
} catch (\Throwable $e) {
$result['error'] = $e->getMessage();
}
return $result;
}
// 查询方法(示例)
public static function queryLogs($userId, $currency, $type = null, $startTime = null, $endTime = null)
{
$model = new static;
$query = $model->setSuffix('_'.strtolower($currency))->where('currency', $currency)
->where('user_id', intval($userId))
->order('created_at', 'desc');
if ($type) {
if($type == '99999'){
$query->whereIn('type', [
\app\enum\BalanceType::OUTPUT_REWARD->value,
\app\enum\BalanceType::WITHDRAW_REWARD->value,
\app\enum\BalanceType::PRODUCT_INCOME->value,
\app\enum\BalanceType::AGENT_COMMISSION->value,
\app\enum\BalanceType::DIFFERENTIAL_COMMISSION->value
]);
}else{
$query->where('type', intval($type));
}
}
if ($startTime) {
// 支持传入时间戳或日期字符串
//$startTimestamp = is_numeric($startTime) ? intval($startTime) : strtotime($startTime);
$query->where('created_at', '>=', $startTime);
}
if ($endTime) {
// 支持传入时间戳或日期字符串
//$endTimestamp = is_numeric($endTime) ? intval($endTime) : strtotime($endTime);
$query->where('created_at', '<=', $endTime);
}
$limit = 10;
if(request()){
$limit = input('limit',10);
}
return $query->paginate($limit);
}
// 创建表结构
protected static function createTableStructure(string $table): bool
{
if (self::tableExists($table)) {
return false;
}
$columns = [];
foreach (self::TABLE_SCHEMA as $column => $definition) {
if (strpos($definition, 'PRIMARY KEY') === false) {
$columns[] = "`{$column}` {$definition}";
}
}
$primaryKey = self::TABLE_SCHEMA['PRIMARY KEY'] ?? 'PRIMARY KEY (`id`)';
$sql = "CREATE TABLE `{$table}` (" .
implode(', ', $columns) . ", " .
$primaryKey .
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci";
Db::execute($sql);
return true;
}
}