在实现匹配交易的过程中,当用户发起卖出物品订单时,需要冻结该物品,防止用户在订单处理期间使用、交易或消耗这些物品。目前GameItem模块缺少物品冻结机制,需要设计并实现完整的物品冻结功能。
is_bound)、交易状态(tradable)ItemTransactionLog记录所有物品流转TRANSACTION_TYPE、ITEM_BIND_TYPE等状态管理在item_users表中新增冻结状态字段:
ALTER TABLE `kku_item_users`
ADD COLUMN `is_frozen` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否冻结(0:未冻结, 1:已冻结)',
ADD COLUMN `freeze_log_id` int DEFAULT NULL COMMENT '冻结日志ID,关联kku_item_freeze_logs表',
ADD INDEX `idx_frozen_status` (`user_id`, `is_frozen`),
ADD INDEX `idx_freeze_log` (`freeze_log_id`);
冻结拆堆机制说明:
拆堆示例:
原始状态:
ItemUser: user_id=1, item_id=100, quantity=1000, is_frozen=0
冻结200个后:
ItemUser: user_id=1, item_id=100, quantity=200, is_frozen=1, freeze_log_id=123
ItemUser: user_id=1, item_id=100, quantity=800, is_frozen=0
解冻后(可选择合并或保持独立):
选择1 - 合并:ItemUser: user_id=1, item_id=100, quantity=1000, is_frozen=0
选择2 - 独立:两条记录都保持is_frozen=0
说明:ItemInstance表不需要扩展冻结字段,因为ItemInstance是物品实例表,记录的是物品的基本信息和属性,而不涉及物品归属关系。冻结功能是针对用户拥有的物品进行的操作,应该在ItemUser表中实现。
创建专门的冻结记录表用于追踪:
CREATE TABLE `kku_item_freeze_logs` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '记录ID,主键',
`user_id` int NOT NULL COMMENT '用户ID',
`item_id` int NOT NULL COMMENT '物品ID',
`instance_id` int DEFAULT NULL COMMENT '物品实例ID(单独属性物品)',
`quantity` int NOT NULL COMMENT '冻结数量',
`action_type` tinyint NOT NULL COMMENT '操作类型(1:冻结, 2:解冻)',
`reason` varchar(255) NOT NULL COMMENT '操作原因',
`source_id` int DEFAULT NULL COMMENT '操作方记录ID',
`source_type` varchar(50) DEFAULT NULL COMMENT '操作类型(如:order, admin, system等)',
`operator_id` int DEFAULT NULL COMMENT '操作员ID(系统操作为NULL)',
`created_at` timestamp NULL DEFAULT NULL COMMENT '操作时间',
PRIMARY KEY (`id`),
KEY `idx_user_item` (`user_id`, `item_id`),
KEY `idx_source` (`source_type`, `source_id`),
KEY `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物品冻结记录表';
// app/Module/GameItems/Enums/FREEZE_ACTION_TYPE.php
class FREEZE_ACTION_TYPE
{
const FREEZE = 1; // 冻结
const UNFREEZE = 2; // 解冻
public static function all(): array
{
return [
self::FREEZE => '冻结',
self::UNFREEZE => '解冻',
];
}
}
// app/Module/GameItems/Enums/FREEZE_REASON_TYPE.php
class FREEZE_REASON_TYPE
{
const TRADE_ORDER = 'trade_order'; // 交易订单
const ADMIN_FREEZE = 'admin_freeze'; // 管理员冻结
const SYSTEM_FREEZE = 'system_freeze'; // 系统冻结
const AUCTION = 'auction'; // 拍卖
const MAIL_ATTACHMENT = 'mail_attachment'; // 邮件附件
public static function all(): array
{
return [
self::TRADE_ORDER => '交易订单',
self::ADMIN_FREEZE => '管理员冻结',
self::SYSTEM_FREEZE => '系统冻结',
self::AUCTION => '拍卖',
self::MAIL_ATTACHMENT => '邮件附件',
];
}
}
// 在ItemUser模型中添加字段和方法
protected $fillable = [
// ... 现有字段
'is_frozen',
'freeze_log_id',
];
protected $casts = [
// ... 现有字段
'is_frozen' => 'boolean',
];
/**
* 检查是否为冻结状态
*/
public function isFrozen(): bool
{
return $this->is_frozen;
}
/**
* 检查是否可用(未冻结)
*/
public function isAvailable(): bool
{
return !$this->is_frozen;
}
/**
* 获取关联的冻结日志
*/
public function freezeLog(): BelongsTo
{
return $this->belongsTo(ItemFreezeLog::class, 'freeze_log_id');
}
/**
* 获取用户指定物品的可用数量(排除冻结的堆叠)
*/
public static function getAvailableQuantity(int $userId, int $itemId, ?int $instanceId = null): int
{
$query = static::where('user_id', $userId)
->where('item_id', $itemId)
->where('is_frozen', false);
if ($instanceId) {
$query->where('instance_id', $instanceId);
} else {
$query->whereNull('instance_id');
}
return $query->sum('quantity');
}
说明:ItemInstance模型不需要扩展冻结相关字段和方法,因为ItemInstance表记录的是物品实例的基本信息,冻结状态应该在ItemUser表中管理。
// app/Module/GameItems/Logics/ItemFreeze.php
class ItemFreeze
{
/**
* 冻结统一属性物品(拆堆模式)
*
* 实现逻辑:
* 1. 查找用户可用的物品堆叠(is_frozen=false)
* 2. 从可用堆叠中扣除冻结数量
* 3. 创建新的冻结堆叠记录(is_frozen=true)
* 4. 记录冻结日志
*/
public static function freezeNormalItem(
int $userId,
int $itemId,
int $quantity,
string $reason,
?int $sourceId = null,
?string $sourceType = null
): Res;
/**
* 冻结单独属性物品(拆堆模式)
* 注:单独属性物品数量始终为1,冻结时直接标记is_frozen=true
*/
public static function freezeUniqueItem(
int $userId,
int $itemId,
int $instanceId,
string $reason,
?int $sourceId = null,
?string $sourceType = null
): Res;
/**
* 解冻统一属性物品(合堆模式)
*
* 实现逻辑:
* 1. 通过freeze_log_id找到冻结的堆叠记录
* 2. 将冻结堆叠标记为可用(is_frozen=false)或删除
* 3. 尝试与现有可用堆叠合并
* 4. 记录解冻日志
*/
public static function unfreezeByLogId(int $freezeLogId): Res;
/**
* 检查用户可用物品数量(排除冻结堆叠)
*/
public static function getAvailableQuantity(
int $userId,
int $itemId,
?int $instanceId = null
): int;
/**
* 验证用户是否有足够的可用物品
*/
public static function checkAvailableQuantity(
int $userId,
int $itemId,
int $requiredQuantity,
?int $instanceId = null
): bool;
}
// 在ItemService中添加冻结相关方法
public static function freezeItem(
int $userId,
int $itemId,
?int $instanceId,
int $quantity,
string $reason,
array $options = []
): array;
public static function unfreezeItem(
int $userId,
int $itemId,
?int $instanceId,
int $quantity,
string $reason,
array $options = []
): array;
public static function getAvailableQuantity(
int $userId,
int $itemId
): int;
public static function getFrozenItems(
int $userId,
array $filters = []
): SupportCollection;
文档创建时间:2025年06月12日 版本:v1.3 状态:设计阶段 修正说明: