|
|
@@ -23,16 +23,35 @@
|
|
|
### 3.1 数据库设计
|
|
|
|
|
|
#### 3.1.1 ItemUser表扩展
|
|
|
-在`item_users`表中新增冻结相关字段:
|
|
|
+在`item_users`表中新增冻结状态字段:
|
|
|
|
|
|
```sql
|
|
|
ALTER TABLE `kku_item_users`
|
|
|
-ADD COLUMN `frozen_quantity` int NOT NULL DEFAULT 0 COMMENT '冻结数量',
|
|
|
+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`, `frozen_quantity`),
|
|
|
+ADD INDEX `idx_frozen_status` (`user_id`, `is_frozen`),
|
|
|
ADD INDEX `idx_freeze_log` (`freeze_log_id`);
|
|
|
```
|
|
|
|
|
|
+**冻结拆堆机制说明**:
|
|
|
+- 冻结时采用拆"堆"模式:将原有堆叠拆分为冻结部分和可用部分两个独立的ItemUser记录
|
|
|
+- 例如:1000个物品堆叠,冻结200个时,拆分为:200个(is_frozen=1)+ 800个(is_frozen=0)
|
|
|
+- 冻结的堆叠不能使用、消耗或交易
|
|
|
+
|
|
|
+**拆堆示例**:
|
|
|
+```
|
|
|
+原始状态:
|
|
|
+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表中实现。
|
|
|
|
|
|
#### 3.1.2 冻结记录表
|
|
|
@@ -109,24 +128,29 @@ class FREEZE_REASON_TYPE
|
|
|
// 在ItemUser模型中添加字段和方法
|
|
|
protected $fillable = [
|
|
|
// ... 现有字段
|
|
|
- 'frozen_quantity',
|
|
|
+ 'is_frozen',
|
|
|
'freeze_log_id',
|
|
|
];
|
|
|
|
|
|
+protected $casts = [
|
|
|
+ // ... 现有字段
|
|
|
+ 'is_frozen' => 'boolean',
|
|
|
+];
|
|
|
+
|
|
|
/**
|
|
|
- * 获取可用数量(总数量 - 冻结数量)
|
|
|
+ * 检查是否为冻结状态
|
|
|
*/
|
|
|
-public function getAvailableQuantityAttribute(): int
|
|
|
+public function isFrozen(): bool
|
|
|
{
|
|
|
- return max(0, $this->quantity - $this->frozen_quantity);
|
|
|
+ return $this->is_frozen;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 检查是否有冻结物品
|
|
|
+ * 检查是否可用(未冻结)
|
|
|
*/
|
|
|
-public function hasFrozenItems(): bool
|
|
|
+public function isAvailable(): bool
|
|
|
{
|
|
|
- return $this->frozen_quantity > 0;
|
|
|
+ return !$this->is_frozen;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -136,6 +160,24 @@ 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表中管理。
|
|
|
@@ -148,7 +190,13 @@ public function freezeLog(): BelongsTo
|
|
|
class ItemFreeze
|
|
|
{
|
|
|
/**
|
|
|
- * 冻结统一属性物品
|
|
|
+ * 冻结统一属性物品(拆堆模式)
|
|
|
+ *
|
|
|
+ * 实现逻辑:
|
|
|
+ * 1. 查找用户可用的物品堆叠(is_frozen=false)
|
|
|
+ * 2. 从可用堆叠中扣除冻结数量
|
|
|
+ * 3. 创建新的冻结堆叠记录(is_frozen=true)
|
|
|
+ * 4. 记录冻结日志
|
|
|
*/
|
|
|
public static function freezeNormalItem(
|
|
|
int $userId,
|
|
|
@@ -160,8 +208,8 @@ class ItemFreeze
|
|
|
): Res;
|
|
|
|
|
|
/**
|
|
|
- * 冻结单独属性物品
|
|
|
- * 注:单独属性物品的冻结状态仍然记录在ItemUser表中,通过instance_id关联
|
|
|
+ * 冻结单独属性物品(拆堆模式)
|
|
|
+ * 注:单独属性物品数量始终为1,冻结时直接标记is_frozen=true
|
|
|
*/
|
|
|
public static function freezeUniqueItem(
|
|
|
int $userId,
|
|
|
@@ -173,40 +221,34 @@ class ItemFreeze
|
|
|
): Res;
|
|
|
|
|
|
/**
|
|
|
- * 解冻统一属性物品
|
|
|
- * 注:解冻由冻结发起方处理,通过freeze_log_id定位冻结记录
|
|
|
+ * 解冻统一属性物品(合堆模式)
|
|
|
+ *
|
|
|
+ * 实现逻辑:
|
|
|
+ * 1. 通过freeze_log_id找到冻结的堆叠记录
|
|
|
+ * 2. 将冻结堆叠标记为可用(is_frozen=false)或删除
|
|
|
+ * 3. 尝试与现有可用堆叠合并
|
|
|
+ * 4. 记录解冻日志
|
|
|
*/
|
|
|
- public static function unfreezeNormalItem(
|
|
|
- int $userId,
|
|
|
- int $itemId,
|
|
|
- int $quantity,
|
|
|
- int $freezeLogId
|
|
|
- ): Res;
|
|
|
+ public static function unfreezeByLogId(int $freezeLogId): Res;
|
|
|
|
|
|
/**
|
|
|
- * 解冻单独属性物品
|
|
|
- * 注:解冻由冻结发起方处理,通过freeze_log_id定位冻结记录
|
|
|
+ * 检查用户可用物品数量(排除冻结堆叠)
|
|
|
*/
|
|
|
- public static function unfreezeUniqueItem(
|
|
|
+ public static function getAvailableQuantity(
|
|
|
int $userId,
|
|
|
int $itemId,
|
|
|
- int $instanceId,
|
|
|
- int $freezeLogId
|
|
|
- ): Res;
|
|
|
+ ?int $instanceId = null
|
|
|
+ ): int;
|
|
|
|
|
|
/**
|
|
|
- * 检查用户可用物品数量
|
|
|
+ * 验证用户是否有足够的可用物品
|
|
|
*/
|
|
|
public static function checkAvailableQuantity(
|
|
|
int $userId,
|
|
|
int $itemId,
|
|
|
- int $requiredQuantity
|
|
|
+ int $requiredQuantity,
|
|
|
+ ?int $instanceId = null
|
|
|
): bool;
|
|
|
-
|
|
|
- /**
|
|
|
- * 根据冻结日志ID解冻物品
|
|
|
- */
|
|
|
- public static function unfreezeByLogId(int $freezeLogId): Res;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
@@ -253,9 +295,10 @@ public static function getFrozenItems(
|
|
|
- **订单失败时**:解冻物品并记录失败原因
|
|
|
|
|
|
### 4.2 与现有验证集成
|
|
|
-- **消耗物品前**:检查可用数量(总数量-冻结数量)
|
|
|
-- **交易物品前**:验证物品未被冻结
|
|
|
-- **使用物品前**:确认物品处于可用状态
|
|
|
+- **消耗物品前**:只查询is_frozen=false的堆叠,确保不消耗冻结物品
|
|
|
+- **交易物品前**:验证物品堆叠未被冻结(is_frozen=false)
|
|
|
+- **使用物品前**:确认物品堆叠处于可用状态(is_frozen=false)
|
|
|
+- **物品查询**:默认只显示可用物品,冻结物品需要特殊查询
|
|
|
|
|
|
### 4.3 解冻机制
|
|
|
- **主动解冻**:由冻结发起方(如交易系统、管理员等)主动调用解冻方法
|
|
|
@@ -284,26 +327,31 @@ public static function getFrozenItems(
|
|
|
## 6. 注意事项
|
|
|
|
|
|
### 6.1 数据一致性
|
|
|
-- 冻结操作必须在事务中执行
|
|
|
-- 确保冻结数量不超过实际拥有数量
|
|
|
-- 解冻时验证冻结记录的有效性
|
|
|
+- 冻结操作必须在事务中执行,确保拆堆操作的原子性
|
|
|
+- 确保冻结数量不超过实际可用数量(is_frozen=false的堆叠总和)
|
|
|
+- 解冻时验证冻结记录的有效性,防止重复解冻
|
|
|
+- 拆堆和合堆操作需要保证数量的准确性
|
|
|
|
|
|
### 6.2 性能考虑
|
|
|
-- 冻结状态查询需要适当的索引支持
|
|
|
-- 大量物品冻结时考虑批量操作
|
|
|
-- 定时任务的执行频率和性能影响
|
|
|
+- 冻结状态查询需要适当的索引支持(user_id, is_frozen)
|
|
|
+- 拆堆操作会增加ItemUser记录数量,需要考虑存储空间
|
|
|
+- 可用数量查询需要聚合计算,建议添加缓存机制
|
|
|
+- 大量物品冻结时考虑批量操作优化
|
|
|
|
|
|
### 6.3 业务规则
|
|
|
-- 冻结物品不能被消耗、交易或使用
|
|
|
-- 解冻操作由冻结发起方负责处理
|
|
|
-- 通过freeze_log_id确保冻结和解冻操作的对应关系
|
|
|
+- 冻结的堆叠(is_frozen=true)不能被消耗、交易或使用
|
|
|
+- 解冻操作由冻结发起方负责处理,通过freeze_log_id定位
|
|
|
+- 拆堆时需要保证原堆叠数量 = 冻结堆叠数量 + 剩余可用堆叠数量
|
|
|
+- 解冻时可以选择与现有可用堆叠合并或保持独立
|
|
|
- 不同冻结原因(source_type)可能有不同的处理逻辑
|
|
|
+- 交易日志不记录冻结相关信息,冻结信息由冻结记录表单独管理
|
|
|
|
|
|
---
|
|
|
|
|
|
*文档创建时间:2025年06月12日*
|
|
|
-*版本:v1.2*
|
|
|
+*版本:v1.3*
|
|
|
*状态:设计阶段*
|
|
|
*修正说明:*
|
|
|
- *v1.1:移除ItemInstance表的冻结扩展,因为ItemInstance是物品实例表,不涉及物品归属关系*
|
|
|
- *v1.2:简化设计,移除自动解冻逻辑,解冻由发起方处理;冻结记录表使用source_id/source_type;简化ItemUser表字段*
|
|
|
+- *v1.3:采用冻结即拆"堆"的模式,使用is_frozen字段标识冻结状态;交易日志不记录冻结信息*
|