Переглянути джерело

修复GameItems冻结实现文档中的错误和逻辑问题

- 修复字段命名不一致问题:freeze_log_id -> frozen_log_id
- 统一枚举定义方式为class+const,与现有TRANSACTION_TYPE保持一致
- 增加批量冻结/解冻操作逻辑
- 增加过期物品冻结处理机制
- 增加异常处理和数据一致性保障
- 优化数据库索引设计,提高查询性能
- 完善业务规则和注意事项
- 更新文档版本至v1.6
notfff 7 місяців тому
батько
коміт
5501a8595c
1 змінених файлів з 159 додано та 28 видалено
  1. 159 28
      app/Module/GameItems/Docs/冻结实现.md

+ 159 - 28
app/Module/GameItems/Docs/冻结实现.md

@@ -28,9 +28,11 @@
 ```sql
 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 COLUMN `frozen_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`);
+ADD INDEX `idx_frozen_log` (`frozen_log_id`),
+ADD INDEX `idx_user_item_frozen` (`user_id`, `item_id`, `is_frozen`),
+ADD INDEX `idx_expire_frozen` (`expire_at`, `is_frozen`);
 ```
 
 **冻结拆堆机制说明**:
@@ -44,7 +46,7 @@ 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=200, is_frozen=1, frozen_log_id=123
 ItemUser: user_id=1, item_id=100, quantity=800, is_frozen=0
 
 解冻后(选择独立):
@@ -84,16 +86,45 @@ CREATE TABLE `kku_item_freeze_logs` (
 // app/Module/GameItems/Enums/FREEZE_ACTION_TYPE.php
 namespace App\Module\GameItems\Enums;
 
-use UCore\Enum\EnumCore;
-use UCore\Enum\EnumExpression;
-use UCore\Enum\EnumToInt;
-
-enum FREEZE_ACTION_TYPE: int
+/**
+ * 物品冻结操作类型枚举
+ */
+class FREEZE_ACTION_TYPE
 {
-    use EnumCore, EnumExpression, EnumToInt;
+    /**
+     * 冻结
+     */
+    const FREEZE = 1;
+
+    /**
+     * 解冻
+     */
+    const UNFREEZE = 2;
 
-    case FREEZE = 1;    // 冻结
-    case UNFREEZE = 2;  // 解冻
+    /**
+     * 获取所有操作类型
+     *
+     * @return array
+     */
+    public static function all(): array
+    {
+        return [
+            self::FREEZE => '冻结',
+            self::UNFREEZE => '解冻',
+        ];
+    }
+
+    /**
+     * 获取操作类型名称
+     *
+     * @param int $value
+     * @return string
+     */
+    public static function getName(int $value): string
+    {
+        $map = self::all();
+        return $map[$value] ?? '未知操作';
+    }
 }
 ```
 
@@ -102,19 +133,63 @@ enum FREEZE_ACTION_TYPE: int
 // app/Module/GameItems/Enums/FREEZE_REASON_TYPE.php
 namespace App\Module\GameItems\Enums;
 
-use UCore\Enum\EnumCore;
-use UCore\Enum\EnumExpression;
-use UCore\Enum\EnumToInt;
-
-enum FREEZE_REASON_TYPE: int
+/**
+ * 物品冻结原因类型枚举
+ */
+class FREEZE_REASON_TYPE
 {
-    use EnumCore, EnumExpression, EnumToInt;
+    /**
+     * 交易订单
+     */
+    const TRADE_ORDER = 1;
+
+    /**
+     * 管理员冻结
+     */
+    const ADMIN_FREEZE = 2;
+
+    /**
+     * 系统冻结
+     */
+    const SYSTEM_FREEZE = 3;
+
+    /**
+     * 拍卖
+     */
+    const AUCTION = 4;
+
+    /**
+     * 邮件附件
+     */
+    const MAIL_ATTACHMENT = 5;
+
+    /**
+     * 获取所有冻结原因
+     *
+     * @return array
+     */
+    public static function all(): array
+    {
+        return [
+            self::TRADE_ORDER => '交易订单',
+            self::ADMIN_FREEZE => '管理员冻结',
+            self::SYSTEM_FREEZE => '系统冻结',
+            self::AUCTION => '拍卖',
+            self::MAIL_ATTACHMENT => '邮件附件',
+        ];
+    }
 
-    case TRADE_ORDER = 1;      // 交易订单
-    case ADMIN_FREEZE = 2;    // 管理员冻结
-    case SYSTEM_FREEZE = 3;  // 系统冻结
-    case AUCTION = 4;        // 拍卖
-    case MAIL_ATTACHMENT = 5; // 邮件附件
+    /**
+     * 获取冻结原因名称
+     *
+     * @param int $value
+     * @return string
+     */
+    public static function getName(int $value): string
+    {
+        $map = self::all();
+        return $map[$value] ?? '未知原因';
+    }
 }
 ```
 
@@ -126,7 +201,7 @@ enum FREEZE_REASON_TYPE: int
 protected $fillable = [
     // ... 现有字段
     'is_frozen',
-    'freeze_log_id',
+    'frozen_log_id',
 ];
 
 protected $casts = [
@@ -155,7 +230,7 @@ public function isAvailable(): bool
  */
 public function freezeLog(): BelongsTo
 {
-    return $this->belongsTo(ItemFreezeLog::class, 'freeze_log_id');
+    return $this->belongsTo(ItemFreezeLog::class, 'frozen_log_id');
 }
 
 /**
@@ -221,7 +296,7 @@ class ItemFreeze
      * 解冻统一属性物品(独立模式)
      *
      * 实现逻辑:
-     * 1. 通过freeze_log_id找到冻结的堆叠记录
+     * 1. 通过frozen_log_id找到冻结的堆叠记录
      * 2. 将冻结堆叠标记为可用(is_frozen=false)
      * 3. 记录解冻日志
      * 注意:解冻后不与其他堆叠合并,保持独立
@@ -246,6 +321,48 @@ class ItemFreeze
         int $requiredQuantity,
         ?int $instanceId = null
     ): bool;
+
+    /**
+     * 批量冻结物品
+     *
+     * @param int $userId 用户ID
+     * @param array $items 物品列表 [['item_id' => 1, 'quantity' => 10], ...]
+     * @param int $reason 冻结原因
+     * @param int|null $sourceId 来源ID
+     * @param string|null $sourceType 来源类型
+     * @return array 冻结结果
+     */
+    public static function batchFreezeItems(
+        int $userId,
+        array $items,
+        int $reason,
+        ?int $sourceId = null,
+        ?string $sourceType = null
+    ): array;
+
+    /**
+     * 检查冻结物品是否过期并处理
+     *
+     * @param int $userId 用户ID
+     * @return int 处理的过期冻结物品数量
+     */
+    public static function handleExpiredFrozenItems(int $userId): int;
+
+    /**
+     * 验证冻结操作的合法性
+     *
+     * @param int $userId 用户ID
+     * @param int $itemId 物品ID
+     * @param int $quantity 冻结数量
+     * @param int|null $instanceId 实例ID
+     * @return bool 是否可以冻结
+     */
+    public static function validateFreezeOperation(
+        int $userId,
+        int $itemId,
+        int $quantity,
+        ?int $instanceId = null
+    ): bool;
 }
 ```
 
@@ -299,7 +416,10 @@ public static function getFrozenItems(
 
 ### 4.3 解冻机制
 - **主动解冻**:由冻结发起方(如交易系统、管理员等)主动调用解冻方法
-- **日志追踪**:通过freeze_log_id关联冻结记录,确保解冻操作的准确性
+- **日志追踪**:通过frozen_log_id关联冻结记录,确保解冻操作的准确性
+- **过期处理**:冻结物品过期时,需要特殊处理逻辑,避免过期物品仍处于冻结状态
+- **批量解冻**:支持批量解冻操作,提高系统性能
+- **异常恢复**:提供异常情况下的冻结状态恢复机制
 
 ## 5. 实现优先级
 
@@ -337,16 +457,26 @@ public static function getFrozenItems(
 
 ### 6.3 业务规则
 - 冻结的堆叠(is_frozen=true)不能被消耗、交易或使用
-- 解冻操作由冻结发起方负责处理,通过freeze_log_id定位
+- 解冻操作由冻结发起方负责处理,通过frozen_log_id定位
 - 拆堆时需要保证原堆叠数量 = 冻结堆叠数量 + 剩余可用堆叠数量
 - 解冻时可以选择与现有可用堆叠合并或保持独立
 - 不同冻结原因(source_type)可能有不同的处理逻辑
 - 交易日志不记录冻结相关信息,冻结信息由冻结记录表单独管理
+- 冻结物品过期时,需要先解冻再处理过期逻辑,避免数据不一致
+- 批量操作时需要考虑事务回滚,确保部分失败时的数据一致性
+- 冻结记录需要定期清理,避免历史数据过多影响性能
+
+### 6.4 异常处理
+- **重复冻结检查**:避免对已冻结的物品重复冻结
+- **数量不足处理**:冻结数量超过可用数量时的错误处理
+- **过期物品冻结**:已过期物品不允许冻结操作
+- **孤儿记录清理**:定期清理无效的冻结记录和孤儿数据
+- **并发操作控制**:防止并发冻结/解冻操作导致的数据不一致
 
 ---
 
 *文档创建时间:2025年06月12日*
-*版本:v1.3*
+*版本:v1.6*
 *状态:设计阶段*
 *修正说明:*
 - *v1.1:移除ItemInstance表的冻结扩展,因为ItemInstance是物品实例表,不涉及物品归属关系*
@@ -354,3 +484,4 @@ public static function getFrozenItems(
 - *v1.3:采用冻结即拆"堆"的模式,使用is_frozen字段标识冻结状态;交易日志不记录冻结信息*
 - *v1.4:调整枚举定义使用PHP 8.1+语法;修正方法返回类型;统一术语*
 - *v1.5:更新解冻机制:解冻后不合并堆叠,保持独立*
+- *v1.6:修复字段命名不一致问题(freeze_log_id -> frozen_log_id);统一枚举定义方式为class+const;增加批量操作、过期处理、异常处理等逻辑;优化数据库索引设计*