|
|
@@ -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;增加批量操作、过期处理、异常处理等逻辑;优化数据库索引设计*
|