Browse Source

Merge remote-tracking branch 'origin/prod'

dongasai 6 months ago
parent
commit
85eee67f19
33 changed files with 2186 additions and 178 deletions
  1. 1 1
      .augment-guidelines
  2. 1 0
      .gitignore
  3. 169 0
      AiWork/202507/050204-修复URS推荐关系缓存断代问题.md
  4. 231 0
      AiWork/202507/050354-优化URS达人等级更新命令输出.md
  5. 150 0
      AiWork/202507/050357-添加URS活跃状态更新定时任务.md
  6. 116 0
      AiWork/202507/050415-修复getTodayStats使用mapping_time判定.md
  7. 155 0
      AiWork/202507/050425-修复URS用户活跃状态数据不一致问题.md
  8. 110 0
      AiWork/2025年07月/05日0300-改进URS关系缓存重建命令支持指定用户处理.md
  9. 1 1
      app/Module/AppGame/Handler/Matchexchange/MyHandler.php
  10. 10 8
      app/Module/AppGame/Handler/Promotion/InfoHandler.php
  11. 19 10
      app/Module/Farm/Logics/CropLogic.php
  12. 49 35
      app/Module/Farm/Logics/DisasterLogic.php
  13. 2 2
      app/Module/Mex/AdminControllers/MexOrderController.php
  14. 4 0
      app/Module/Mex/Logic/MexMatchLogic.php
  15. 7 3
      app/Module/Mex/Logic/MexOrderLogic.php
  16. 6 6
      app/Module/Mex/Services/MexOrderService.php
  17. 1 1
      app/Module/UrsPromotion/AdminControllers/Actions/ViewReferralChainAction.php
  18. 1 1
      app/Module/UrsPromotion/AdminControllers/Actions/ViewReferralTreeAction.php
  19. 275 0
      app/Module/UrsPromotion/Commands/TestRelationCacheFixCommand.php
  20. 253 14
      app/Module/UrsPromotion/Commands/UrsRebuildRelationCacheCommand.php
  21. 17 3
      app/Module/UrsPromotion/Commands/UrsUpdateActiveStatusCommand.php
  22. 60 4
      app/Module/UrsPromotion/Commands/UrsUpdateTalentLevelCommand.php
  23. 15 1
      app/Module/UrsPromotion/Dtos/UrsUserTalentDto.php
  24. 280 33
      app/Module/UrsPromotion/Logics/UrsRelationCacheLogic.php
  25. 4 1
      app/Module/UrsPromotion/Models/UrsUserMapping.php
  26. 4 3
      app/Module/UrsPromotion/Models/UrsUserRelationCache.php
  27. 9 0
      app/Module/UrsPromotion/Providers/UrsPromotionServiceProvider.php
  28. 125 6
      app/Module/UrsPromotion/Services/UrsActiveUserService.php
  29. 54 5
      app/Module/UrsPromotion/Services/UrsReferralService.php
  30. 25 14
      app/Module/UrsPromotion/Services/UrsTalentService.php
  31. 2 2
      docker-compose.dev.yml
  32. 2 2
      docker-compose.devprod.yml
  33. 28 22
      tests/Unit/AppGame/Handler/Promotion/TodayStatsLogicTest.php

+ 1 - 1
.augment-guidelines

@@ -2,7 +2,7 @@
 
 ## 项目概述
 - 这是一个基于Laravel 11的农场游戏系统
-- 项目本地使用Docker运行,访问地址:http://kku_laravel.local.gd
+- 项目本地使用Docker运行
 - 当前项目处理维护期,不得随意对数据库的表结构进行修改,不得对涉及游戏数值的表进行修改
 - 在容器内运行命令
 

+ 1 - 0
.gitignore

@@ -31,3 +31,4 @@ DEV.*
 .roo/
 ./test*.php
 000-default.conf
+.augment/rules/prod.md

+ 169 - 0
AiWork/202507/050204-修复URS推荐关系缓存断代问题.md

@@ -0,0 +1,169 @@
+# 修复URS推荐关系缓存断代问题
+
+**任务时间**: 2025年07月05日 02:04  
+**任务类型**: Bug修复  
+**模块**: UrsPromotion/UrsRelationCacheLogic  
+
+## 问题描述
+
+用户反馈"未进入农场,跳过缓存生成"会造成推荐关系的"断代"问题。
+
+### 问题场景
+假设推荐链:A (未进入农场) → B (已进入农场) → C (已进入农场) → D (已进入农场)
+
+**原有逻辑问题**:
+- 当B用户尝试生成关系缓存时,发现推荐人A未进入农场
+- 系统直接跳过B的缓存生成
+- 导致B、C、D都无法建立与A的关系缓存
+- **结果**:整个推荐链断裂,A永远无法获得下级奖励
+
+### 业务影响
+1. **奖励分发断裂**:推荐人无法获得下级奖励
+2. **数据不完整**:关系缓存表缺失大量有效推荐关系
+3. **用户体验差**:推荐人看不到下级数据
+4. **业务逻辑错误**:违背推荐系统基本原则
+
+## 解决方案
+
+### 1. 修改缓存生成逻辑
+
+**原有逻辑**:
+```php
+// 如果农场用户ID为0,跳过缓存生成
+if ($farmUserId <= 0 || $farmReferrerId <= 0) {
+    Log::info("URS用户 {$ursUserId} 或推荐人 {$ursReferrerId} 未进入农场,跳过缓存生成");
+    return true;
+}
+```
+
+**新逻辑**:
+- 如果当前用户未进入农场,跳过缓存生成
+- 如果推荐人未进入农场,创建**占位缓存记录**,避免断代
+- 通过URS关系链向上查找已进入农场的上级
+
+### 2. 占位缓存机制
+
+当推荐人未进入农场时:
+1. 创建占位的直接关系缓存(`related_user_id = 0`)
+2. 通过URS关系链向上递归查找
+3. 为已进入农场的上级创建有效缓存
+4. 为未进入农场的上级创建占位缓存
+
+### 3. 推荐人进入农场时的修复机制
+
+当推荐人后来进入农场时:
+1. 更新所有占位缓存记录(`related_user_id` 从0更新为实际农场用户ID)
+2. 更新路径中的占位符(将路径中的'0'替换为实际农场用户ID)
+3. 为该用户的所有下级重新生成缓存,修复断代问题
+
+## 实现详情
+
+### 1. 核心方法修改
+
+#### `generateUserRelationCache()` 方法
+- 区分当前用户和推荐人的进入农场状态
+- 推荐人未进入农场时创建占位缓存
+- 调用新的递归方法处理间接关系
+
+#### 新增辅助方法
+1. `generateIndirectRelationsFromUrsChain()` - 基于URS关系链生成间接关系
+2. `generateIndirectRelationsFromExistingCache()` - 基于已有缓存生成间接关系
+3. `updatePlaceholderPaths()` - 更新路径中的占位符
+4. `regenerateDownstreamCaches()` - 重新生成下级缓存
+
+### 2. 路径构建方法
+1. `buildFarmUserPath()` - 构建农场用户路径
+2. `buildUrsPath()` - 构建URS用户路径
+3. `buildCombinedFarmPath()` - 构建组合农场用户路径
+4. `buildCombinedUrsPath()` - 构建组合URS用户路径
+
+### 3. 缓存更新机制
+
+#### `updateFarmUserIdInCache()` 方法增强
+- 更新占位缓存记录(`related_user_id` 从0更新为实际ID)
+- 更新路径中的占位符
+- 为所有下级重新生成缓存
+
+## 测试工具
+
+创建了专门的测试命令 `TestRelationCacheFixCommand`:
+
+```bash
+# 检查断代问题
+php artisan urs:test-relation-cache-fix --check
+
+# 修复断代问题
+php artisan urs:test-relation-cache-fix --fix
+
+# 显示统计信息
+php artisan urs:test-relation-cache-fix --stats
+
+# 测试特定用户
+php artisan urs:test-relation-cache-fix --user-id=24127
+```
+
+### 测试功能
+1. **检查断代问题**:查找有推荐关系但无缓存的用户
+2. **修复断代问题**:批量重新生成缺失的缓存
+3. **统计信息**:显示缓存完整性统计
+4. **特定用户测试**:详细测试单个用户的缓存生成
+
+## 技术特点
+
+### 1. 向后兼容
+- 保持现有缓存结构不变
+- 新增占位机制不影响现有查询逻辑
+- 渐进式修复,不需要全量重建
+
+### 2. 性能优化
+- 递归深度限制(最多20代)
+- 批量更新减少数据库操作
+- 智能跳过已处理的记录
+
+### 3. 数据完整性
+- 占位缓存确保关系链完整
+- 自动修复机制确保数据一致性
+- 详细日志记录便于问题追踪
+
+### 4. 业务逻辑正确性
+- 解决断代问题,确保奖励正常分发
+- 保持推荐关系的完整性
+- 支持推荐人后续进入农场的场景
+
+## 预期效果
+
+1. **解决断代问题**:推荐人未进入农场不再导致整个推荐链断裂
+2. **完整的关系缓存**:所有有效推荐关系都有对应的缓存记录
+3. **正常的奖励分发**:推荐人进入农场后能正常获得下级奖励
+4. **良好的用户体验**:推荐数据完整,用户满意度提升
+
+## 数据库索引优化
+
+### 问题发现
+在实现过程中发现原有的 `idx_user_relation` 唯一索引存在问题:
+- **原索引**:`UNIQUE KEY idx_user_relation (user_id, related_user_id)`
+- **问题**:当多个用户的推荐人都未进入农场时,都会产生 `related_user_id = 0` 的占位记录
+- **冲突**:违反唯一约束,导致插入失败
+
+### 解决方案
+移除有问题的索引,增加包含深度的组合索引:
+- **移除**:`idx_user_relation (user_id, related_user_id)`
+- **新增**:`idx_user_relation_depth (user_id, related_user_id, depth)`
+
+### 优化效果
+1. **允许占位记录**:多个用户可以有 `related_user_id = 0` 的记录
+2. **防止真正重复**:同一用户、同一上级、同一深度不能重复
+3. **保持性能**:索引仍然提供良好的查询性能
+4. **业务逻辑正确**:符合推荐关系的实际业务需求
+
+## 风险控制
+
+1. **占位缓存标识**:使用`related_user_id = 0`明确标识占位记录
+2. **递归深度限制**:防止无限递归导致性能问题
+3. **异常处理**:完善的错误处理和日志记录
+4. **测试工具**:提供完整的测试和验证工具
+5. **索引优化**:合理的数据库索引设计,避免约束冲突
+
+## 总结
+
+本次修复彻底解决了URS推荐关系缓存的断代问题,通过占位缓存机制确保推荐链的完整性,同时提供了完善的修复和测试工具。修复后的系统能够正确处理推荐人未进入农场的场景,确保业务逻辑的正确性和用户体验的完整性。

+ 231 - 0
AiWork/202507/050354-优化URS达人等级更新命令输出.md

@@ -0,0 +1,231 @@
+# URS达人等级更新命令输出优化
+
+## 任务概述
+
+**时间**: 2025年07月05日 03:54  
+**任务**: 优化URS达人等级更新命令的输出,增加活跃直推数据显示  
+**问题**: 用户39296没有升级,需要查看详细的升级条件和活跃用户数据
+
+## 问题分析
+
+### 1. 初始问题
+- 用户39296有521个直推,691个团队总人数,但达人等级仍为0(青铜)
+- 命令输出信息不够详细,无法看到活跃用户数据和升级条件
+
+### 2. 根本原因
+- **活跃用户数据缺失**: UrsActiveUserService中checkUserActivity方法使用了不存在的last_activity_time字段
+- **活动时间字段位置错误**: 活动时间在UserInfo表中,不在User表中
+- **命令注册缺失**: UrsUpdateActiveStatusCommand没有在ServiceProvider中注册
+- **输出信息不完整**: 缺少活跃用户数据和升级条件详情
+
+## 解决方案
+
+### 1. 修复活跃用户检查逻辑
+
+#### 1.1 修正活动时间字段访问
+**文件**: `app/Module/UrsPromotion/Services/UrsActiveUserService.php`
+
+```php
+// 修改前:直接访问User模型的last_activity_time(不存在)
+if (!$user->last_activity_time) {
+    return false;
+}
+
+// 修改后:通过UserInfo关联访问
+$userInfo = $user->info;
+if (!$userInfo || !$userInfo->last_activity_time) {
+    return false;
+}
+```
+
+#### 1.2 优化数据预加载
+**文件**: `app/Module/UrsPromotion/Models/UrsUserMapping.php`
+
+```php
+// 在getUsersNeedActivityCheck方法中预加载用户信息关联
+->with(['user', 'user.info']) // 预加载用户和用户信息关联
+```
+
+### 2. 添加活跃直推专用方法
+
+#### 2.1 新增getActiveDirectMembers方法
+**文件**: `app/Module/UrsPromotion/Services/UrsActiveUserService.php`
+
+```php
+/**
+ * 获取指定URS用户的活跃直推成员(仅直推层级)
+ * 
+ * 针对达人等级升级条件优化,只统计直推活跃用户
+ * 相比getActiveTeamMembers方法,此方法只关注直推层级,性能更优
+ */
+public static function getActiveDirectMembers(int $ursUserId): array
+{
+    // 只获取直推成员(第1层级)
+    $directMembers = UrsReferralService::getDirectReferrals($ursUserId);
+    
+    if (empty($directMembers)) {
+        return [
+            'active_direct_count' => 0,
+            'active_direct_members' => []
+        ];
+    }
+    
+    // 获取活跃的直推成员
+    $activeDirectIds = UrsUserMapping::getActiveUrsUserIds($directMembers);
+    
+    return [
+        'active_direct_count' => count($activeDirectIds),
+        'active_direct_members' => $activeDirectMembers
+    ];
+}
+```
+
+### 3. 注册活跃状态更新命令
+
+#### 3.1 添加命令注册
+**文件**: `app/Module/UrsPromotion/Providers/UrsPromotionServiceProvider.php`
+
+```php
+$this->commands([
+    // ... 其他命令
+    \App\Module\UrsPromotion\Commands\UrsUpdateActiveStatusCommand::class,
+]);
+```
+
+### 4. 扩展DTO支持活跃数据
+
+#### 4.1 扩展UrsUserTalentDto
+**文件**: `app/Module/UrsPromotion/Dtos/UrsUserTalentDto.php`
+
+```php
+/**
+ * @var int 活跃直推人数
+ */
+public int $activeDirectCount = 0;
+
+/**
+ * @var int 活跃团队总人数
+ */
+public int $activeTotalCount = 0;
+
+// 修改fromModel方法签名,支持活跃数据参数
+public static function fromModel(
+    UrsUserTalent $model, 
+    ?array $currentConfig = null, 
+    ?array $nextConfig = null, 
+    int $activeDirectCount = 0, 
+    int $activeTotalCount = 0
+): self
+```
+
+#### 4.2 修改服务层传递活跃数据
+**文件**: `app/Module/UrsPromotion/Services/UrsTalentService.php`
+
+```php
+// 获取活跃直推成员统计(优化性能,只获取直推活跃数据)
+$activeDirectStats = UrsActiveUserService::getActiveDirectMembers($ursUserId);
+
+// 为了显示完整信息,也获取活跃团队总数
+$activeTeamStats = UrsActiveUserService::getActiveTeamMembers($ursUserId);
+
+// 在创建DTO时传递活跃数据
+return UrsUserTalentDto::fromModel(
+    $talent, 
+    $currentConfig, 
+    $nextConfigArray,
+    $activeDirectStats['active_direct_count'],
+    $activeTeamStats['active_total_count']
+);
+```
+
+### 5. 优化命令输出显示
+
+#### 5.1 重新设计输出格式
+**文件**: `app/Module/UrsPromotion/Commands/UrsUpdateTalentLevelCommand.php`
+
+新的输出包含:
+1. **基础信息表格**: URS用户ID、达人等级、等级名称、最后更新时间
+2. **团队统计表格**: 显示总数、活跃数、活跃率
+3. **升级条件检查**: 显示下一等级的要求和当前状态
+
+```php
+// 团队统计表格
+$this->table(['统计项', '总数', '活跃数', '活跃率'], [
+    [
+        '直推人数', 
+        $talentDto->directCount, 
+        $talentDto->activeDirectCount,
+        $talentDto->directCount > 0 ? round($talentDto->activeDirectCount * 100 / $talentDto->directCount, 1) . '%' : '0%'
+    ],
+    // ... 其他统计项
+]);
+
+// 升级条件检查
+$this->table(['条件', '要求', '当前', '状态'], [
+    [
+        '直推人数',
+        $nextLevel['direct_count_required'],
+        $talentDto->directCount,
+        $directMet ? '✅ 已满足' : '❌ 未满足'
+    ],
+    [
+        '活跃直推',
+        $nextLevel['active_direct_required'] ?? 0,
+        $talentDto->activeDirectCount,
+        $activeDirectMet ? '✅ 已满足' : '❌ 未满足'
+    ]
+]);
+```
+
+## 执行结果
+
+### 1. 活跃状态更新成功
+```bash
+php artisan urs:update-active-status --limit=50
+# 成功更新50个用户,其中50个被标记为活跃
+```
+
+### 2. 用户39296成功升级
+```bash
+php artisan urs:update-talent-level 39296
+```
+
+**结果**:
+- **等级**: 从0(青铜)升级到1(白银)
+- **直推**: 521人(112人活跃,21.5%活跃率)
+- **团队**: 691人(235人活跃,34%活跃率)
+- **下一等级条件**:
+  - 直推人数: 521/500 ✅ 已满足
+  - 团队总人数: 691/3000 ❌ 未满足
+  - 活跃直推: 112/500 ❌ 未满足
+
+## 技术改进
+
+### 1. 性能优化
+- 新增`getActiveDirectMembers`方法,只查询直推活跃用户,避免查询整个团队
+- 预加载用户信息关联,减少N+1查询问题
+
+### 2. 代码质量
+- 修复了活动时间字段访问错误
+- 扩展DTO支持活跃数据传递
+- 优化命令输出,信息更加详细和直观
+
+### 3. 用户体验
+- 清晰显示活跃率统计
+- 详细的升级条件检查
+- 直观的状态标识(✅❌)
+
+## 后续建议
+
+1. **定时任务**: 建议每日运行`urs:update-active-status`更新用户活跃状态
+2. **监控**: 关注活跃用户比例,如果过低需要检查活动时间更新机制
+3. **优化**: 可以考虑增加缓存机制,提高大批量用户等级更新的性能
+
+## 相关文件
+
+- `app/Module/UrsPromotion/Services/UrsActiveUserService.php`
+- `app/Module/UrsPromotion/Services/UrsTalentService.php`
+- `app/Module/UrsPromotion/Dtos/UrsUserTalentDto.php`
+- `app/Module/UrsPromotion/Commands/UrsUpdateTalentLevelCommand.php`
+- `app/Module/UrsPromotion/Models/UrsUserMapping.php`
+- `app/Module/UrsPromotion/Providers/UrsPromotionServiceProvider.php`

+ 150 - 0
AiWork/202507/050357-添加URS活跃状态更新定时任务.md

@@ -0,0 +1,150 @@
+# URS活跃状态更新定时任务配置
+
+## 任务概述
+
+**时间**: 2025年07月05日 03:57  
+**任务**: 为UrsUpdateActiveStatusCommand添加任务调度,设置在每日1:05执行,后台运行  
+**状态**: ✅ 已完成
+
+## 实施内容
+
+### 1. 添加定时任务调度
+
+**文件**: `app/Module/UrsPromotion/Providers/UrsPromotionServiceProvider.php`
+
+在现有的定时任务配置中添加了URS用户活跃状态更新任务:
+
+```php
+// 每天01:05执行用户活跃状态更新
+$schedule->command('urs:update-active-status')
+    ->dailyAt('01:05')
+    ->description('URS用户活跃状态更新 - 基于最近15天活动时间更新用户活跃状态')
+    ->withoutOverlapping() // 防止重复执行
+    ->runInBackground(); // 后台运行
+```
+
+### 2. 任务配置详情
+
+- **执行时间**: 每日 01:05
+- **命令**: `php artisan urs:update-active-status`
+- **描述**: URS用户活跃状态更新 - 基于最近15天活动时间更新用户活跃状态
+- **防重复**: `withoutOverlapping()` - 防止任务重复执行
+- **后台运行**: `runInBackground()` - 任务在后台执行,不阻塞其他任务
+
+### 3. 执行时间安排
+
+当前URS模块的定时任务时间安排:
+- **00:30** - URS合伙人分红任务
+- **01:05** - URS用户活跃状态更新任务
+
+这样的时间安排确保:
+1. 避免与其他任务冲突
+2. 在用户活动较少的凌晨时段执行
+3. 为后续可能的达人等级更新任务预留时间
+
+## 验证结果
+
+### 1. 定时任务列表确认
+
+通过 `php artisan schedule:list` 命令验证,任务已成功注册:
+
+```
+5  1 * * *  php artisan urs:update-active-status ....... Next Due: 21小时后
+```
+
+### 2. 命令功能测试
+
+使用 `--dry-run` 模式测试命令功能:
+
+```bash
+php artisan urs:update-active-status --dry-run
+```
+
+**测试结果**:
+- 总用户数:3124
+- 活跃用户:1727 (55.28%)
+- 不活跃用户:1397
+- 最近24小时更新:1830
+- 需要检查的用户:1293
+
+命令运行正常,统计信息准确。
+
+## 技术特性
+
+### 1. 防重复执行
+使用 `withoutOverlapping()` 确保同一时间只有一个活跃状态更新任务在运行,避免:
+- 数据库锁冲突
+- 重复更新同一用户
+- 系统资源浪费
+
+### 2. 后台执行
+使用 `runInBackground()` 确保任务在后台执行,特点:
+- 不阻塞其他定时任务
+- 提高系统整体性能
+- 适合长时间运行的任务
+
+### 3. 详细描述
+添加了清晰的任务描述,便于:
+- 系统管理员了解任务用途
+- 日志记录和监控
+- 问题排查和维护
+
+## 监控建议
+
+### 1. 日志监控
+建议监控以下日志:
+- 任务执行开始和结束时间
+- 处理的用户数量
+- 活跃状态变更统计
+- 错误和异常情况
+
+### 2. 性能监控
+关注以下性能指标:
+- 任务执行时长
+- 数据库查询性能
+- 内存使用情况
+- 系统负载影响
+
+### 3. 业务监控
+监控业务指标:
+- 活跃用户比例变化
+- 活跃状态更新成功率
+- 数据一致性检查
+
+## 后续优化
+
+### 1. 批处理优化
+如果用户数量继续增长,可以考虑:
+- 调整批处理大小(当前默认1000)
+- 分时段执行(如分多个时间点执行)
+- 增加并发处理能力
+
+### 2. 缓存优化
+可以考虑添加缓存机制:
+- 缓存用户活动时间查询结果
+- 缓存活跃状态计算结果
+- 减少数据库查询压力
+
+### 3. 监控告警
+建议添加监控告警:
+- 任务执行失败告警
+- 执行时间过长告警
+- 活跃用户比例异常告警
+
+## 相关文件
+
+- `app/Module/UrsPromotion/Providers/UrsPromotionServiceProvider.php` - 定时任务配置
+- `app/Module/UrsPromotion/Commands/UrsUpdateActiveStatusCommand.php` - 命令实现
+- `app/Module/UrsPromotion/Services/UrsActiveUserService.php` - 活跃用户服务
+
+## 部署说明
+
+此更改已包含在代码中,部署后定时任务将自动生效。无需额外的配置或手动设置crontab。
+
+Laravel的定时任务通过以下方式运行:
+```bash
+# 在服务器crontab中添加(如果尚未添加)
+* * * * * cd /path/to/project && php artisan schedule:run >> /dev/null 2>&1
+```
+
+这样Laravel会自动管理所有注册的定时任务,包括新添加的URS活跃状态更新任务。

+ 116 - 0
AiWork/202507/050415-修复getTodayStats使用mapping_time判定.md

@@ -0,0 +1,116 @@
+# 修复 getTodayStats 方法使用 mapping_time 时间判定
+
+**任务时间**: 2025年07月05日 04:15
+**任务类型**: Bug修复
+**模块**: AppGame/Handler/Promotion
+
+## 问题描述
+
+用户反馈 `getTodayStats` 方法不能正确工作,缓存的时间判定有问题,应该使用 `UrsUserMapping` 表的 `mapping_time` 字段进行时间判定,而不是使用缓存表的 `created_at` 字段。
+
+## 问题分析
+
+### 原有实现的问题
+1. **错误的时间基准**: 原来的实现使用 `UrsUserRelationCache.created_at` 作为今日新增的判定依据
+2. **时间语义不符**: `created_at` 表示关系缓存记录的创建时间,而不是用户实际进入农场的时间
+3. **数据不准确**: 缓存可能在用户进入农场之前就已经创建(占位记录),导致统计不准确
+
+### 正确的实现方式
+应该使用 `UrsUserMapping.mapping_time` 字段,这个字段才真正表示用户进入农场的时间。
+
+## 修复方案
+
+### 1. 修改查询逻辑
+将原来基于缓存表 `created_at` 的查询改为基于映射表 `mapping_time` 的关联查询:
+
+**修改前**:
+```php
+$todayRelations = UrsUserRelationCache::where('related_user_id', $farmUserId)
+    ->whereDate('created_at', today())
+    ->selectRaw('...')
+    ->first();
+```
+
+**修改后**:
+```php
+$todayRelations = UrsUserRelationCache::where('related_user_id', $farmUserId)
+    ->join('urs_promotion_user_mappings', 'kku_urs_promotion_user_relation_cache.urs_user_id', '=', 'urs_promotion_user_mappings.urs_user_id')
+    ->where('urs_promotion_user_mappings.status', UrsUserMapping::STATUS_VALID)
+    ->whereDate('urs_promotion_user_mappings.mapping_time', today())
+    ->selectRaw('...')
+    ->first();
+```
+
+### 2. 修改的文件
+- **文件**: `app/Module/AppGame/Handler/Promotion/InfoHandler.php`
+- **方法**: `getTodayStats()`
+- **添加导入**: `use App\Module\UrsPromotion\Models\UrsUserMapping;`
+
+### 3. 核心修改内容
+1. 添加 JOIN 查询关联 `urs_promotion_user_mappings` 表
+2. 添加状态过滤条件 `status = 1`(有效状态)
+3. 将时间过滤条件从 `created_at` 改为 `mapping_time`
+4. 更新字段引用,使用完整的表名前缀避免歧义
+
+## 测试验证
+
+### 1. 更新测试文件
+- **文件**: `tests/Unit/AppGame/Handler/Promotion/TodayStatsLogicTest.php`
+- **新增测试方法**: `test_optimized_query_logic_with_mapping_time()`
+- **更新测试方法**: `test_query_bindings_with_mapping_time()`
+
+### 2. 测试结果
+```bash
+PHPUnit 11.5.20 by Sebastian Bergmann and contributors.
+.......                                                             7 / 7 (100%)
+Time: 00:00.496, Memory: 46.50 MB
+OK (7 tests, 16 assertions)
+```
+
+### 3. 测试覆盖
+- ✅ SQL查询构建逻辑
+- ✅ 查询参数绑定验证
+- ✅ JOIN关联查询逻辑
+- ✅ 时间过滤条件验证
+- ✅ 状态过滤条件验证
+
+## 修复效果
+
+### 1. 数据准确性
+- **时间基准正确**: 基于用户实际进入农场的时间(`mapping_time`)
+- **状态过滤**: 只统计有效状态的映射关系
+- **避免占位数据**: 不会统计到占位的缓存记录
+
+### 2. 查询性能
+- **单次查询**: 通过 JOIN 查询一次性获取结果
+- **索引利用**: 利用现有的索引结构
+- **查询效率**: 相比原来的多次查询,性能更优
+
+### 3. 代码质量
+- **逻辑清晰**: 查询意图更明确
+- **数据一致**: 与业务逻辑保持一致
+- **可维护性**: 代码更容易理解和维护
+
+## 注意事项
+
+### 1. 数据依赖
+- 依赖 `urs_promotion_user_mappings` 表的数据完整性
+- 需要确保 `mapping_time` 字段正确记录用户进入农场的时间
+
+### 2. 兼容性
+- 保持返回数据格式不变
+- 保持异常处理逻辑不变
+- 不影响其他相关功能
+
+### 3. 监控建议
+- 监控查询性能变化
+- 验证统计数据的准确性
+- 关注 `mapping_time` 字段的数据质量
+
+## 相关说明
+
+### getActiveStats 方法
+`getActiveStats` 方法没有类似问题,因为它基于用户的活跃时间(`last_activity_time`)进行统计,时间语义是正确的。
+
+### 缓存机制
+此修复不影响关系缓存的生成和维护机制,只是修正了统计查询的时间基准。

+ 155 - 0
AiWork/202507/050425-修复URS用户活跃状态数据不一致问题.md

@@ -0,0 +1,155 @@
+# 修复 URS 用户活跃状态数据不一致问题
+
+**任务时间**: 2025年07月05日 04:25
+**任务类型**: Bug修复 + 功能增强
+**模块**: UrsPromotion
+
+## 问题描述
+
+用户反馈项目才上线几天,没到不活跃的限制天数(15天),但是 `UrsUserMapping` 表中有102个用户的 `is_active` 字段被标记为0(不活跃),这是不正确的。
+
+## 问题分析
+
+### 1. 数据检查结果
+- **总用户数**: 3124
+- **活跃用户**: 3022 (96.73%)
+- **不活跃用户**: 102 (3.27%)
+- **活跃判定标准**: 最近15天有活动
+
+### 2. 深入分析发现的问题
+通过手动检查不活跃用户的详细信息,发现:
+
+```
+用户 44578: 最后活动时间 2025-07-04 23:03:23 (距今 -0.22天)
+用户 10387: 最后活动时间 2025-07-05 00:18:18 (距今 -0.17天)  
+用户 10003: 最后活动时间 2025-07-05 02:45:04 (距今 -0.07天)
+```
+
+**关键发现**:
+- 这些用户的最后活动时间都是最近的(今天或昨天)
+- 手动调用 `UrsActiveUserService::checkUserActivity()` 返回 `true`(活跃)
+- 但数据库中 `is_active` 字段为 `0`(不活跃)
+- **数据库状态与实际检查结果不一致**
+
+### 3. 根本原因分析
+1. **定时任务逻辑限制**: `getUsersNeedActivityCheck()` 方法只检查 `last_activity_check` 超过1天的用户
+2. **初始化数据问题**: 可能在数据初始化时某些用户状态设置不正确
+3. **更新时机问题**: 定时任务可能在某个时间点执行异常,导致部分用户状态未正确更新
+
+## 解决方案
+
+### 1. 立即修复数据
+执行强制更新脚本,修复所有状态不一致的用户:
+
+```php
+// 获取所有标记为不活跃的用户
+$inactiveUsers = UrsUserMapping::where('is_active', 0)
+    ->where('status', 1)
+    ->with('user.info')
+    ->get();
+
+// 检查并更新实际应该活跃的用户
+foreach($inactiveUsers as $mapping) {
+    if ($mapping->user) {
+        $isActive = UrsActiveUserService::checkUserActivity($mapping->user);
+        if ($isActive) {
+            $mapping->update([
+                'is_active' => 1,
+                'last_activity_check' => now(),
+                'active_days_count' => 1,
+            ]);
+        }
+    }
+}
+```
+
+**修复结果**:
+- 应该活跃的用户: 100个
+- 实际更新的用户: 100个
+- 修复后活跃比例: 99.94% (3122/3124)
+- 剩余不活跃用户: 2个(确实无活动记录)
+
+### 2. 增强命令功能
+为防止类似问题再次发生,增强 `urs:update-active-status` 命令:
+
+#### 2.1 添加 `--force` 选项
+```bash
+php artisan urs:update-active-status --force
+```
+- 强制检查所有用户,忽略 `last_activity_check` 时间限制
+- 适用于数据修复和全量检查场景
+
+#### 2.2 新增 `forceUpdateActiveStatus` 方法
+```php
+public static function forceUpdateActiveStatus(int $limit = 1000): array
+{
+    // 获取所有有效用户映射,忽略last_activity_check限制
+    $mappings = UrsUserMapping::where('status', UrsUserMapping::STATUS_VALID)
+        ->with(['user', 'user.info'])
+        ->limit($limit)
+        ->get();
+    
+    // 逐个检查并更新活跃状态
+    foreach ($mappings as $mapping) {
+        $isActive = self::checkUserActivity($mapping->user);
+        $mapping->update([
+            'is_active' => $isActive ? 1 : 0,
+            'last_activity_check' => now(),
+            'active_days_count' => $isActive ? 1 : 0,
+        ]);
+    }
+}
+```
+
+### 3. 修改的文件
+1. **命令文件**: `app/Module/UrsPromotion/Commands/UrsUpdateActiveStatusCommand.php`
+   - 添加 `--force` 选项
+   - 更新帮助信息
+   - 增强处理逻辑
+
+2. **服务文件**: `app/Module/UrsPromotion/Services/UrsActiveUserService.php`
+   - 新增 `forceUpdateActiveStatus()` 方法
+   - 支持强制更新所有用户状态
+
+## 验证结果
+
+### 1. 数据修复验证
+```
+修复前: 活跃用户 3022 (96.73%), 不活跃用户 102
+修复后: 活跃用户 3122 (99.94%), 不活跃用户 2
+```
+
+### 2. 功能测试验证
+```bash
+# 测试强制更新功能
+php artisan urs:update-active-status --force --limit=10 --dry-run
+# ✅ 命令执行正常,功能可用
+```
+
+### 3. 剩余不活跃用户验证
+剩余的2个不活跃用户确实无活动记录,状态正确:
+```
+用户 46260: 无活动记录
+用户 46262: 无活动记录
+```
+
+## 预防措施
+
+### 1. 监控建议
+- 定期检查活跃状态统计,关注异常波动
+- 监控定时任务执行日志,确保正常运行
+- 设置活跃比例告警,低于预期时及时处理
+
+### 2. 运维建议
+- 每周执行一次强制更新: `php artisan urs:update-active-status --force`
+- 在数据迁移或系统升级后执行数据一致性检查
+- 保持定时任务的稳定执行
+
+### 3. 代码改进建议
+- 考虑在 `getUsersNeedActivityCheck` 方法中增加数据一致性检查
+- 添加活跃状态变更的详细日志记录
+- 考虑实现活跃状态的实时更新机制
+
+## 总结
+
+此次问题的核心是**数据库状态与实际业务逻辑不一致**,通过强制更新修复了100个用户的错误状态,并增强了命令功能以防止类似问题再次发生。现在系统的活跃用户比例达到99.94%,符合项目刚上线几天的预期状态。

+ 110 - 0
AiWork/2025年07月/05日0300-改进URS关系缓存重建命令支持指定用户处理.md

@@ -0,0 +1,110 @@
+# 改进URS关系缓存重建命令支持指定用户处理
+
+## 任务时间
+- 开始时间:2025年07月05日 03:00:04 CST
+- 完成时间:2025年07月05日 03:30:00 CST
+
+## 任务背景
+用户反馈URS推荐系统中的缓存生成存在逻辑bug,缓存不应该基于缓存生成,要从关系表中读取数据。同时需要改进UrsRebuildRelationCacheCommand命令,支持指定用户处理功能。
+
+## 问题分析
+1. **缓存生成逻辑错误**:原逻辑中存在基于现有缓存生成新缓存的问题,导致循环依赖
+2. **路径链不完整**:用户44578有多级推荐关系,但缓存只生成了部分级别
+3. **缺少指定用户处理功能**:命令只能全量重建,无法针对特定用户操作
+
+## 解决方案
+
+### 1. 新增指定用户处理功能
+在`UrsRebuildRelationCacheCommand`中添加以下参数:
+- `--user=` : 指定单个URS用户ID
+- `--users=` : 指定多个URS用户ID(逗号分隔)
+- `--clear` : 清除指定用户的缓存
+
+### 2. 修复缓存生成逻辑
+- 移除`generateIndirectRelationsFromExistingCache`方法
+- 修改`generateIndirectRelationsFromUrsChain`方法,完全基于URS推荐关系表生成缓存
+- 修复路径链构建逻辑,确保完整的路径链传递
+
+### 3. 完善命令功能
+- 添加用户ID验证
+- 改进错误处理和用户反馈
+- 添加进度条显示
+- 更新命令帮助和使用示例
+
+## 实现细节
+
+### 命令参数扩展
+```php
+protected $signature = 'urs:rebuild-relation-cache 
+                        {--batch-size=100 : 批处理大小}
+                        {--check : 仅检查完整性,不重建}
+                        {--fix : 修复发现的问题}
+                        {--user= : 指定URS用户ID,仅处理该用户}
+                        {--users= : 指定多个URS用户ID,用逗号分隔}
+                        {--clear : 清除指定用户的缓存(需配合--user或--users使用)}';
+```
+
+### 逻辑修复
+1. **移除错误的缓存依赖逻辑**:
+   ```php
+   // 原错误逻辑(已删除)
+   $upperRelations = UrsUserRelationCache::where('urs_user_id', $ursReferrerId)->get();
+   
+   // 修复后的逻辑
+   $this->generateIndirectRelationsFromUrsChain($ursUserId, $farmUserId, $ursReferrerId, 1);
+   ```
+
+2. **修复路径链构建**:
+   ```php
+   // 构建完整的路径链
+   $newUrsPath = empty($currentUrsPath) ? (string)$upperUrsReferrerId : $currentUrsPath . ',' . $upperUrsReferrerId;
+   $newFarmPath = empty($currentFarmPath) ? $upperFarmPart : $currentFarmPath . ',' . $upperFarmPart;
+   ```
+
+## 测试验证
+
+### 测试用例:用户44578
+**推荐关系链**:44578 -> 44572 -> 10387 -> 10002 -> 1
+
+**修复前**:只有1级缓存
+```
+Depth 1: related_urs_id=44572, path=44572
+```
+
+**修复后**:完整的4级缓存
+```
+Depth 1: related_urs_id=44572, urs_path=44572, farm_path=40124
+Depth 2: related_urs_id=10387, urs_path=44572,10387, farm_path=40124,39172
+Depth 3: related_urs_id=10002, urs_path=44572,10387,10002, farm_path=40124,39172,39186
+Depth 4: related_urs_id=1, urs_path=44572,10387,10002,1, farm_path=40124,39172,39186,0
+```
+
+### 命令测试
+```bash
+# 指定单个用户
+php artisan urs:rebuild-relation-cache --user=44578
+
+# 指定多个用户
+php artisan urs:rebuild-relation-cache --users=44578,10387
+
+# 清除指定用户缓存
+php artisan urs:rebuild-relation-cache --user=44578 --clear
+
+# 无效用户ID处理
+php artisan urs:rebuild-relation-cache --user=999999
+# 输出:所有指定的用户ID都无效
+```
+
+## 代码变更
+- 修改文件:`app/Module/UrsPromotion/Commands/UrsRebuildRelationCacheCommand.php`
+- 修改文件:`app/Module/UrsPromotion/Logics/UrsRelationCacheLogic.php`
+- 新增方法:`handleSpecificUsers()`, `getSpecifiedUserIds()`, `validateUserIds()`, `clearSpecificUsersCache()`, `rebuildSpecificUsersCache()`
+- 删除方法:`generateIndirectRelationsFromExistingCache()`, `buildCombinedFarmPath()`, `buildCombinedUrsPath()`, `buildFarmUserPath()`, `buildUrsPath()`
+
+## 总结
+1. **成功修复**了缓存生成的逻辑bug,改为完全基于URS推荐关系表生成
+2. **新增**了指定用户处理功能,提高了命令的灵活性
+3. **修复**了路径链构建问题,确保缓存数据的完整性和正确性
+4. **完善**了错误处理和用户体验
+
+该改进解决了用户反馈的核心问题,提升了URS推荐系统缓存的可靠性和维护效率。

+ 1 - 1
app/Module/AppGame/Handler/Matchexchange/MyHandler.php

@@ -57,7 +57,7 @@ class MyHandler extends BaseHandler
             $pageNum = max($pageNum, 1);
 
             // 调用服务获取用户订单列表
-            $result = MexOrderService::getUserOrders($userId, $pageNum, $pageSize);
+            $result = MexOrderService::getUserOrders($userId, $pageNum, $pageSize, $itemId);
 
             // 设置分页信息
             $responsePage = new ResponsePage();

+ 10 - 8
app/Module/AppGame/Handler/Promotion/InfoHandler.php

@@ -7,6 +7,7 @@ use App\Module\UrsPromotion\Services\UrsUserMappingService;
 use App\Module\UrsPromotion\Services\UrsReferralService;
 use App\Module\UrsPromotion\Services\UrsTalentService;
 use App\Module\UrsPromotion\Models\UrsUserRelationCache;
+use App\Module\UrsPromotion\Models\UrsUserMapping;
 use App\Module\User\Services\UserActivityService;
 use App\Module\User\Models\UserInfo;
 use App\Module\Fund\Enums\FUND_TYPE;
@@ -83,8 +84,7 @@ class InfoHandler extends BaseHandler
                 'user_id' => $this->user_id
             ]);
             // 获取活跃用户统计
-
-            $team_active_count = UrsReferralService::getTeamANumber($this->user_id);
+            $team_active_count = UrsReferralService::getTeamANumber($this->user_id,1);
             // 获取收益统计
             $rewardStats = $this->getRewardStats($ursUserId);
 
@@ -193,13 +193,15 @@ class InfoHandler extends BaseHandler
                 ];
             }
 
-            // 使用 UrsUserRelationCache 查询今日新增的关系记录
-            // 查询今日创建的关系缓存记录,按层级统计
-            $todayRelations = UrsUserRelationCache::where('related_user_id', $farmUserId)
-                ->whereDate('created_at', today())
+            // 使用 UrsUserRelationCache 关联 UrsUserMapping 查询今日新增的用户
+            // 基于用户的 mapping_time(进入农场时间)而不是缓存的 created_at 时间
+            $todayRelations = UrsUserRelationCache::where('urs_promotion_user_relation_cache.related_user_id', $farmUserId)
+                ->join('urs_promotion_user_mappings', 'urs_promotion_user_relation_cache.urs_user_id', '=', 'urs_promotion_user_mappings.urs_user_id')
+                ->where('urs_promotion_user_mappings.status', UrsUserMapping::STATUS_VALID)
+                ->whereDate('urs_promotion_user_mappings.mapping_time', today())
                 ->selectRaw('
-                    COUNT(CASE WHEN depth = 1 THEN 1 END) as direct_new_count,
-                    COUNT(CASE WHEN depth <= 3 THEN 1 END) as team_new_count
+                    COUNT(CASE WHEN kku_urs_promotion_user_relation_cache.depth = 1 THEN 1 END) as direct_new_count,
+                    COUNT(CASE WHEN kku_urs_promotion_user_relation_cache.depth <= 3 THEN 1 END) as team_new_count
                 ')
                 ->first();
 

+ 19 - 10
app/Module/Farm/Logics/CropLogic.php

@@ -1473,13 +1473,22 @@ class CropLogic
             // 2. 获取基础产量(使用发芽期确定的产出配置)
             $outputInfo = $this->getOutputInfoByItemId($seed->id, $crop->final_output_item_id);
 
-            // 检查是否有灾害,如果有灾害则使用灾害时的产量区间
-            $hasDisaster = !empty($crop->disasters);
+            // 检查是否有活跃灾害,只有活跃灾害才影响产量
+            $hasActiveDisaster = false;
+            if (!empty($crop->disasters)) {
+                foreach ($crop->disasters as $disaster) {
+                    if (($disaster['status'] ?? '') === 'active') {
+                        $hasActiveDisaster = true;
+                        break;
+                    }
+                }
+            }
+
             $minBaseAmount = $outputInfo['min_amount'];
             $maxBaseAmount = $outputInfo['max_amount'];
 
-            if ($hasDisaster) {
-                // 有灾害时,使用灾害时的产量区间
+            if ($hasActiveDisaster) {
+                // 有活跃灾害时,使用灾害时的产量区间
                 $disasterMinAmount = $outputInfo['disaster_min_amount'] ?? 500;
                 $disasterMaxAmount = $outputInfo['disaster_max_amount'] ?? 2000;
                 $minBaseAmount = $disasterMinAmount;
@@ -1528,8 +1537,8 @@ class CropLogic
 
             // 如果有丰收之神加持,使用最大可能产量
             if ($hasHarvestBuff) {
-                // 丰收之神加持时的最大产量也要考虑灾害影响
-                $maxPossibleAmount = $hasDisaster ?
+                // 丰收之神加持时的最大产量也要考虑活跃灾害影响
+                $maxPossibleAmount = $hasActiveDisaster ?
                     ($outputInfo['disaster_max_amount'] ?? 2000) :
                     $outputInfo['max_amount'];
 
@@ -1543,8 +1552,8 @@ class CropLogic
             $globalMaxOutput = 3000;
             $finalAmount = min($finalAmount, $globalMaxOutput);
 
-            // 如果有灾害,确保产量不超过产出配置的灾害时最高产量(双重保险)
-            if ($hasDisaster) {
+            // 如果有活跃灾害,确保产量不超过产出配置的灾害时最高产量(双重保险)
+            if ($hasActiveDisaster) {
                 $disasterMaxAmount = $outputInfo['disaster_max_amount'] ?? 2000;
                 $finalAmount = min($finalAmount, $disasterMaxAmount);
             }
@@ -1562,7 +1571,7 @@ class CropLogic
                 'final_amount' => $finalAmount,
                 'land_bonus' => $landOutputBonus,
                 'house_bonus' => $houseOutputBonus,
-                'has_disaster' => $hasDisaster,
+                'has_active_disaster' => $hasActiveDisaster,
                 'disaster_penalty' => $disasterPenalty,
                 'has_harvest_buff' => $hasHarvestBuff,
                 'disaster_max_amount' => $outputInfo['disaster_max_amount'] ?? 2000,
@@ -1576,7 +1585,7 @@ class CropLogic
                 'final_amount' => $finalAmount,
                 'land_bonus' => $landOutputBonus,
                 'house_bonus' => $houseOutputBonus,
-                'has_disaster' => $hasDisaster,
+                'has_active_disaster' => $hasActiveDisaster,
                 'disaster_penalty' => $disasterPenalty,
                 'has_harvest_buff' => $hasHarvestBuff,
                 'disaster_max_amount' => $outputInfo['disaster_max_amount'] ?? 2000,

+ 49 - 35
app/Module/Farm/Logics/DisasterLogic.php

@@ -13,6 +13,7 @@ use App\Module\Farm\Models\FarmLand;
 use App\Module\Farm\Models\FarmCropLog;
 use App\Module\Farm\Services\DisasterService;
 use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
 
 /**
@@ -114,10 +115,7 @@ class DisasterLogic
                 return null;
             }
 
-            // 跳过已经有灾害的土地
-            if ($land->status === LAND_STATUS::DISASTER) {
-                return null;
-            }
+            // 注意:根据文档,作物灾害和土地无关,不需要检查土地状态
 
             $userId = $crop->user_id;
             $seed   = $crop->seed;
@@ -277,6 +275,7 @@ class DisasterLogic
         $disasters = array_merge($disasters, $disasterInfos);
         $crop->disasters = $disasters;
 
+        // TODO: 根据文档,作物灾害和土地无关,此处更新土地状态可能需要重新评估
         // 更新土地状态为灾害
         $land = $crop->land;
         $oldLandStatus = $land->status;
@@ -371,12 +370,10 @@ class DisasterLogic
     public function generateDisasters(FarmCrop $crop): string
     {
         try {
-            // 更新检查时间
-            $crop->last_disaster_check_time = now();
+            // 1. 先进行基础检查和计算(不在事务中,减少锁时间)
 
-            // 检查用户是否存在,如果不存在则跳过
+            // 检查用户是否存在
             if (!$crop->user) {
-                $crop->save(); // 仍需更新检查时间
                 Log::warning('作物关联的用户不存在,跳过灾害生成', [
                     'crop_id' => $crop->id,
                     'user_id' => $crop->user_id
@@ -384,41 +381,58 @@ class DisasterLogic
                 return 'skipped';
             }
 
-            // 跳过已经有灾害的土地
-            if ($crop->land->status === LAND_STATUS::DISASTER) {
-                $crop->save(); // 仍需更新检查时间
-                return 'skipped';
-            }
+            // 注意:根据文档,作物灾害和土地无关,不需要检查土地状态
 
-            // 获取相关数据
+            // 获取相关数据并计算灾害
             $disasterResistance = $crop->seed->disaster_resistance ?? null;
-            $landDisasterResistance = ($crop->land->landType->disaster_resistance ?? 0) / 100; // 数据库存储百分比,需要除以100
+            $landDisasterResistance = ($crop->land->landType->disaster_resistance ?? 0) / 100;
             $activeBuffs = $crop->user->buffs->pluck('buff_type')->toArray();
 
             // 尝试生成灾害(支持多种灾害)
             $disasterInfos = $this->tryGenerateDisasterForCrop($crop, $disasterResistance, $landDisasterResistance, $activeBuffs);
 
-            if (!empty($disasterInfos)) {
-                // 应用灾害到作物
-                $this->applyDisastersToCrop($crop, $disasterInfos);
-
-                // 生成灾害后,设置当前阶段不能再产生灾害
-                $crop->can_disaster = false;
-                $crop->save();
-
-                Log::info('单个作物灾害生成成功', [
-                    'crop_id' => $crop->id,
-                    'user_id' => $crop->user_id,
-                    'disaster_count' => count($disasterInfos),
-                    'disaster_types' => array_column($disasterInfos, 'type')
-                ]);
+            // 2. 开启事务进行有锁更新
+            return DB::transaction(function () use ($crop, $disasterInfos) {
+                // 使用行锁重新获取作物,确保数据一致性
+                $lockedCrop = FarmCrop::where('id', $crop->id)
+                    ->lockForUpdate()
+                    ->first();
+
+                // 如果作物不存在或已被软删除,跳过处理
+                if (!$lockedCrop || $lockedCrop->trashed()) {
+                    Log::info('作物已被删除,跳过灾害生成', [
+                        'crop_id' => $crop->id,
+                        'user_id' => $crop->user_id
+                    ]);
+                    return 'skipped';
+                }
 
-                return 'generated';
-            } else {
-                // 没有生成灾害,但需要保存检查时间
-                $crop->save();
-                return 'checked';
-            }
+                // 更新检查时间
+                $lockedCrop->last_disaster_check_time = now();
+
+                // 如果有灾害需要生成,应用到作物
+                if (!empty($disasterInfos)) {
+                    // 应用灾害到作物
+                    $this->applyDisastersToCrop($lockedCrop, $disasterInfos);
+
+                    // 生成灾害后,设置当前阶段不能再产生灾害
+                    $lockedCrop->can_disaster = false;
+                    $lockedCrop->save();
+
+                    Log::info('单个作物灾害生成成功', [
+                        'crop_id' => $lockedCrop->id,
+                        'user_id' => $lockedCrop->user_id,
+                        'disaster_count' => count($disasterInfos),
+                        'disaster_types' => array_column($disasterInfos, 'type')
+                    ]);
+
+                    return 'generated';
+                } else {
+                    // 没有生成灾害,但需要保存检查时间
+                    $lockedCrop->save();
+                    return 'checked';
+                }
+            });
 
         } catch (\Exception $e) {
             Log::error('单个作物灾害生成失败', [

+ 2 - 2
app/Module/Mex/AdminControllers/MexOrderController.php

@@ -18,7 +18,7 @@ use Dcat\Admin\Show;
 
 /**
  * 农贸市场订单管理
- * 
+ *
  * 路由:/admin/mex-orders
  */
 #[Resource('mex-orders', names: 'dcat.admin.mex-orders')]
@@ -86,7 +86,7 @@ class MexOrderController extends AdminController
             });
             $grid->column('price', '价格')->display(function ($value) {
                 return number_format($value, 5);
-            });
+            })->sortable();
             $grid->column('total_amount', '总金额')->display(function ($value) {
                 return $value ? number_format($value, 5) : '-';
             });

+ 4 - 0
app/Module/Mex/Logic/MexMatchLogic.php

@@ -426,6 +426,10 @@ class MexMatchLogic
                 $sellOrders = MexOrder::where('item_id', $itemId)
                     ->where('order_type', OrderType::SELL)
                     ->where('status', OrderStatus::PENDING)
+                    ->where('price', '<=',$priceConfig->min_price) // 价格 <= 最低价格
+                    ->where('quantity', '<=', $priceConfig->protection_threshold) // 数量保护:数量≤保护阈值
+                    ->orderBy('price', 'asc')
+                    ->orderBy('id', 'asc')
                     ->limit($batchSize)
                     ->get();
 

+ 7 - 3
app/Module/Mex/Logic/MexOrderLogic.php

@@ -215,9 +215,13 @@ class MexOrderLogic
      * @param int $pageSize 每页数量
      * @return array 订单列表
      */
-    public static function getUserOrders(int $userId, int $page = 1, int $pageSize = 20): array
+    public static function getUserOrders(int $userId, int $page = 1, int $pageSize = 20, $itemId =  null): array
     {
-        $orders = MexOrder::where('user_id', $userId)
+        $where['user_id'] = $userId;
+        if ($itemId) {
+            $where['item_id'] = $itemId;
+        }
+        $orders = MexOrder::where($where)
             ->orderBy('created_at', 'desc')
             ->paginate($pageSize, ['*'], 'page', $page);
 
@@ -269,7 +273,7 @@ class MexOrderLogic
                     $unfrozenCount++;
                 } catch (\Exception $e) {
                     // 记录错误但继续处理其他冻结记录
-                    \Log::error("解冻物品失败", [
+                    Logger::error("解冻物品失败", [
                         'order_id' => $order->id,
                         'freeze_log_id' => $freezeLog->id,
                         'error' => $e->getMessage()

+ 6 - 6
app/Module/Mex/Services/MexOrderService.php

@@ -10,7 +10,7 @@ use App\Module\Fund\Enums\FUND_CURRENCY_TYPE;
 
 /**
  * 农贸市场订单服务
- * 
+ *
  * 提供订单相关的对外服务接口
  */
 class MexOrderService
@@ -65,20 +65,20 @@ class MexOrderService
 
     /**
      * 获取用户订单列表
-     * 
+     *
      * @param int $userId 用户ID
      * @param int $page 页码
      * @param int $pageSize 每页数量
      * @return array 订单列表
      */
-    public static function getUserOrders(int $userId, int $page = 1, int $pageSize = 20): array
+    public static function getUserOrders(int $userId, int $page = 1, int $pageSize = 20, $itemId = null): array
     {
-        return MexOrderLogic::getUserOrders($userId, $page, $pageSize);
+        return MexOrderLogic::getUserOrders($userId, $page, $pageSize, $itemId);
     }
 
     /**
      * 获取订单详情
-     * 
+     *
      * @param int $userId 用户ID
      * @param int $orderId 订单ID
      * @return array|null 订单详情
@@ -90,7 +90,7 @@ class MexOrderService
 
     /**
      * 获取待撮合的买入订单
-     * 
+     *
      * @param int $itemId 商品ID
      * @param int $limit 限制数量
      * @return array 订单列表

+ 1 - 1
app/Module/UrsPromotion/AdminControllers/Actions/ViewReferralChainAction.php

@@ -203,7 +203,7 @@ class ViewReferralChainAction extends RowAction
                 $html .= "<span class='user-info'>URS-{$user['urs_user_id']}</span>";
 
                 if ($user['farm_user_id']) {
-                    $html .= " → 农场用户: {$user['username']} ({$user['nickname']})";
+                    $html .= " → 农场用户ID: {$user['farm_user_id']} | {$user['username']} ({$user['nickname']})";
                 } else {
                     $html .= " → <span class='text-warning'>未进入农场</span>";
                 }

+ 1 - 1
app/Module/UrsPromotion/AdminControllers/Actions/ViewReferralTreeAction.php

@@ -186,7 +186,7 @@ class ViewReferralTreeAction extends RowAction
         $html .= "<span class='user-info'>URS-{$user['urs_user_id']}</span>";
         
         if ($user['farm_user_id']) {
-            $html .= " → 农场用户: {$user['username']} ({$user['nickname']})";
+            $html .= " → 农场用户ID: {$user['farm_user_id']} | {$user['username']} ({$user['nickname']})";
         } else {
             $html .= " → <span class='text-warning'>未进入农场</span>";
         }

+ 275 - 0
app/Module/UrsPromotion/Commands/TestRelationCacheFixCommand.php

@@ -0,0 +1,275 @@
+<?php
+
+namespace App\Module\UrsPromotion\Commands;
+
+use App\Module\UrsPromotion\Logics\UrsRelationCacheLogic;
+use App\Module\UrsPromotion\Models\UrsUserMapping;
+use App\Module\UrsPromotion\Models\UrsUserReferral;
+use App\Module\UrsPromotion\Models\UrsUserRelationCache;
+use App\Module\UrsPromotion\Services\UrsUserMappingService;
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+
+/**
+ * 测试关系缓存断代修复命令
+ * 
+ * 用于测试和验证关系缓存断代问题的修复效果
+ * php artisan urs:test-relation-cache-fix --check
+ */
+class TestRelationCacheFixCommand extends Command
+{
+    /**
+     * 命令签名
+     */
+    protected $signature = 'urs:test-relation-cache-fix 
+                            {--user-id= : 指定测试的URS用户ID}
+                            {--check : 检查断代问题}
+                            {--fix : 修复断代问题}
+                            {--stats : 显示统计信息}';
+
+    /**
+     * 命令描述
+     */
+    protected $description = '测试URS关系缓存断代修复功能';
+
+    /**
+     * 执行命令
+     */
+    public function handle()
+    {
+        $this->info('=== URS关系缓存断代修复测试 ===');
+
+        if ($this->option('check')) {
+            $this->checkBreakageIssues();
+        } elseif ($this->option('fix')) {
+            $this->fixBreakageIssues();
+        } elseif ($this->option('stats')) {
+            $this->showStatistics();
+        } elseif ($this->option('user-id')) {
+            $this->testSpecificUser((int)$this->option('user-id'));
+        } else {
+            $this->showUsage();
+        }
+    }
+
+    /**
+     * 检查断代问题
+     */
+    private function checkBreakageIssues()
+    {
+        $this->info('检查关系缓存断代问题...');
+
+        // 1. 查找有推荐关系但没有缓存的用户
+        $usersWithReferralButNoCache = DB::select("
+            SELECT r.urs_user_id, r.urs_referrer_id, m.user_id as farm_user_id
+            FROM kku_urs_promotion_user_referrals r
+            LEFT JOIN kku_urs_promotion_user_relation_cache c ON r.urs_user_id = c.urs_user_id
+            LEFT JOIN kku_urs_promotion_user_mappings m ON r.urs_user_id = m.urs_user_id AND m.status = 1
+            WHERE r.status = 1
+            AND c.urs_user_id IS NULL
+            AND m.user_id > 0
+            LIMIT 10
+        ");
+
+        $this->table(
+            ['URS用户ID', '推荐人ID', '农场用户ID', '问题'],
+            array_map(function ($user) {
+                return [
+                    $user->urs_user_id,
+                    $user->urs_referrer_id,
+                    $user->farm_user_id,
+                    '有推荐关系但无缓存'
+                ];
+            }, $usersWithReferralButNoCache)
+        );
+
+        // 2. 查找占位缓存记录
+        $placeholderCaches = UrsUserRelationCache::where('related_user_id', 0)->limit(10)->get();
+        
+        if ($placeholderCaches->count() > 0) {
+            $this->info("\n占位缓存记录:");
+            $this->table(
+                ['用户ID', 'URS用户ID', 'URS推荐人ID', '深度'],
+                $placeholderCaches->map(function ($cache) {
+                    return [
+                        $cache->user_id,
+                        $cache->urs_user_id,
+                        $cache->urs_related_user_id,
+                        $cache->depth
+                    ];
+                })->toArray()
+            );
+        }
+
+        $this->info("\n断代问题检查完成");
+    }
+
+    /**
+     * 修复断代问题
+     */
+    private function fixBreakageIssues()
+    {
+        $this->info('开始修复关系缓存断代问题...');
+
+        $logic = new UrsRelationCacheLogic();
+        
+        // 获取所有有推荐关系但没有缓存的已进入农场用户
+        $problematicUsers = DB::select("
+            SELECT r.urs_user_id, m.user_id as farm_user_id
+            FROM kku_urs_promotion_user_referrals r
+            LEFT JOIN kku_urs_promotion_user_relation_cache c ON r.urs_user_id = c.urs_user_id
+            INNER JOIN kku_urs_promotion_user_mappings m ON r.urs_user_id = m.urs_user_id AND m.status = 1
+            WHERE r.status = 1
+            AND c.urs_user_id IS NULL
+            AND m.user_id > 0
+        ");
+
+        $successCount = 0;
+        $failCount = 0;
+
+        $progressBar = $this->output->createProgressBar(count($problematicUsers));
+        $progressBar->start();
+
+        foreach ($problematicUsers as $user) {
+            if ($logic->generateUserRelationCache($user->urs_user_id)) {
+                $successCount++;
+            } else {
+                $failCount++;
+            }
+            $progressBar->advance();
+        }
+
+        $progressBar->finish();
+
+        $this->info("\n修复完成:");
+        $this->info("成功: {$successCount}");
+        $this->info("失败: {$failCount}");
+        $this->info("总计: " . count($problematicUsers));
+    }
+
+    /**
+     * 显示统计信息
+     */
+    private function showStatistics()
+    {
+        $this->info('=== 关系缓存统计信息 ===');
+
+        // 基础统计
+        $totalReferrals = UrsUserReferral::where('status', UrsUserReferral::STATUS_VALID)->count();
+        $totalMappings = UrsUserMapping::where('status', UrsUserMapping::STATUS_VALID)->count();
+        $totalCaches = UrsUserRelationCache::count();
+        $placeholderCaches = UrsUserRelationCache::where('related_user_id', 0)->count();
+
+        $this->table(
+            ['指标', '数量'],
+            [
+                ['有效推荐关系', $totalReferrals],
+                ['用户映射关系', $totalMappings],
+                ['关系缓存记录', $totalCaches],
+                ['占位缓存记录', $placeholderCaches],
+            ]
+        );
+
+        // 断代问题统计
+        $usersWithReferralButNoCache = DB::selectOne("
+            SELECT COUNT(*) as count
+            FROM kku_urs_promotion_user_referrals r
+            LEFT JOIN kku_urs_promotion_user_relation_cache c ON r.urs_user_id = c.urs_user_id
+            INNER JOIN kku_urs_promotion_user_mappings m ON r.urs_user_id = m.urs_user_id AND m.status = 1
+            WHERE r.status = 1
+            AND c.urs_user_id IS NULL
+            AND m.user_id > 0
+        ");
+
+        $this->info("\n断代问题统计:");
+        $this->info("有推荐关系但无缓存的已进入农场用户: " . $usersWithReferralButNoCache->count);
+        
+        if ($placeholderCaches > 0) {
+            $this->warn("发现 {$placeholderCaches} 条占位缓存记录,表示存在推荐人未进入农场的情况");
+        }
+    }
+
+    /**
+     * 测试特定用户
+     */
+    private function testSpecificUser(int $ursUserId)
+    {
+        $this->info("测试URS用户 {$ursUserId} 的关系缓存...");
+
+        // 显示用户基本信息
+        $referral = UrsUserReferral::where('urs_user_id', $ursUserId)->first();
+        $mapping = UrsUserMapping::where('urs_user_id', $ursUserId)->first();
+        
+        $this->table(
+            ['属性', '值'],
+            [
+                ['URS用户ID', $ursUserId],
+                ['推荐人ID', $referral ? $referral->urs_referrer_id : '无'],
+                ['农场用户ID', $mapping ? $mapping->user_id : '未进入农场'],
+                ['映射状态', $mapping ? ($mapping->status == 1 ? '有效' : '无效') : '不存在'],
+            ]
+        );
+
+        // 显示现有缓存
+        $caches = UrsUserRelationCache::where('urs_user_id', $ursUserId)->get();
+        
+        if ($caches->count() > 0) {
+            $this->info("\n现有关系缓存:");
+            $this->table(
+                ['深度', 'URS推荐人ID', '农场推荐人ID', 'URS路径'],
+                $caches->map(function ($cache) {
+                    return [
+                        $cache->depth,
+                        $cache->urs_related_user_id,
+                        $cache->related_user_id ?: '占位',
+                        $cache->urs_path
+                    ];
+                })->toArray()
+            );
+        } else {
+            $this->warn("该用户没有关系缓存记录");
+        }
+
+        // 重新生成缓存
+        if ($this->confirm('是否重新生成该用户的关系缓存?')) {
+            $logic = new UrsRelationCacheLogic();
+            if ($logic->generateUserRelationCache($ursUserId)) {
+                $this->info("关系缓存重新生成成功");
+                
+                // 显示新的缓存
+                $newCaches = UrsUserRelationCache::where('urs_user_id', $ursUserId)->get();
+                $this->info("\n新的关系缓存:");
+                $this->table(
+                    ['深度', 'URS推荐人ID', '农场推荐人ID', 'URS路径'],
+                    $newCaches->map(function ($cache) {
+                        return [
+                            $cache->depth,
+                            $cache->urs_related_user_id,
+                            $cache->related_user_id ?: '占位',
+                            $cache->urs_path
+                        ];
+                    })->toArray()
+                );
+            } else {
+                $this->error("关系缓存重新生成失败");
+            }
+        }
+    }
+
+    /**
+     * 显示使用说明
+     */
+    private function showUsage()
+    {
+        $this->info('使用说明:');
+        $this->info('  --check          检查断代问题');
+        $this->info('  --fix            修复断代问题');
+        $this->info('  --stats          显示统计信息');
+        $this->info('  --user-id=ID     测试特定用户');
+        $this->info('');
+        $this->info('示例:');
+        $this->info('  php artisan urs:test-relation-cache-fix --check');
+        $this->info('  php artisan urs:test-relation-cache-fix --fix');
+        $this->info('  php artisan urs:test-relation-cache-fix --user-id=24127');
+    }
+}

+ 253 - 14
app/Module/UrsPromotion/Commands/UrsRebuildRelationCacheCommand.php

@@ -7,23 +7,34 @@ use Illuminate\Console\Command;
 
 /**
  * URS用户关系缓存重建命令
- * 
+ *
  * 用于批量重建URS用户关系缓存,提升查询性能
+ *
+ * 使用示例:
+ * php artisan urs:rebuild-relation-cache                    # 重建所有用户缓存
+ * php artisan urs:rebuild-relation-cache --check            # 检查缓存完整性
+ * php artisan urs:rebuild-relation-cache --fix              # 修复发现的问题
+ * php artisan urs:rebuild-relation-cache --user=123         # 重建指定用户缓存
+ * php artisan urs:rebuild-relation-cache --users=123,456    # 重建多个用户缓存
+ * php artisan urs:rebuild-relation-cache --user=123 --clear # 清除指定用户缓存
  */
 class UrsRebuildRelationCacheCommand extends Command
 {
     /**
      * 命令签名
      */
-    protected $signature = 'urs:rebuild-relation-cache 
+    protected $signature = 'urs:rebuild-relation-cache
                             {--batch-size=100 : 批处理大小}
                             {--check : 仅检查完整性,不重建}
-                            {--fix : 修复发现的问题}';
+                            {--fix : 修复发现的问题}
+                            {--user= : 指定URS用户ID,仅处理该用户}
+                            {--users= : 指定多个URS用户ID,用逗号分隔}
+                            {--clear : 清除指定用户的缓存(需配合--user或--users使用)}';
 
     /**
      * 命令描述
      */
-    protected $description = 'URS用户关系缓存重建命令';
+    protected $description = 'URS用户关系缓存重建命令,支持全量重建、指定用户处理、完整性检查和问题修复';
 
     /**
      * 执行命令
@@ -31,7 +42,12 @@ class UrsRebuildRelationCacheCommand extends Command
     public function handle()
     {
         $logic = new UrsRelationCacheLogic();
-        
+
+        // 处理指定用户的缓存操作
+        if ($this->option('user') || $this->option('users')) {
+            return $this->handleSpecificUsers($logic);
+        }
+
         // 仅检查完整性
         if ($this->option('check')) {
             $this->info('开始检查URS关系缓存完整性...');
@@ -63,19 +79,47 @@ class UrsRebuildRelationCacheCommand extends Command
         // 修复问题
         if ($this->option('fix')) {
             $this->info('开始修复URS关系缓存问题...');
-            $result = $logic->fixRelationCacheIssues();
-            
+
+            // 先检查需要修复的数量
+            $integrity = $logic->checkRelationCacheIntegrity();
+            $totalToFix = $integrity['missing_users'] + $integrity['orphaned_caches'];
+
+            if ($totalToFix == 0) {
+                $this->info('没有发现需要修复的问题');
+                return 0;
+            }
+
+            $progressBar = $this->output->createProgressBar($totalToFix);
+            $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% - %message%');
+            $progressBar->setMessage('准备修复...');
+            $progressBar->start();
+
+            $currentProgress = 0;
+
+            // 定义进度回调函数
+            $progressCallback = function($type, $ursUserId, $processed, $total, $fixed) use ($progressBar, &$currentProgress) {
+                $currentProgress++;
+                $typeText = $type === 'missing' ? '修复缺失缓存' : '清理孤立缓存';
+                $progressBar->setMessage("{$typeText} - URS用户ID: {$ursUserId} (已修复: {$fixed})");
+                $progressBar->setProgress($currentProgress);
+            };
+
+            $result = $logic->fixRelationCacheIssues($progressCallback);
+
+            $progressBar->finish();
+            $this->newLine();
+
             if (isset($result['error'])) {
                 $this->error('修复失败: ' . $result['error']);
                 return 1;
             }
-            
+
             $this->info('修复结果:');
             $this->table(['项目', '修复数量'], [
                 ['缺失用户缓存', $result['fixed']['missing_users']],
                 ['孤立缓存清理', $result['fixed']['orphaned_caches']]
             ]);
-            
+
             $this->info('问题修复完成');
             return 0;
         }
@@ -89,12 +133,23 @@ class UrsRebuildRelationCacheCommand extends Command
         }
         
         $this->info("开始重建URS关系缓存,批处理大小: {$batchSize}");
-        
-        $progressBar = $this->output->createProgressBar();
+
+        // 先获取总用户数用于进度条初始化
+        $totalUsers = \App\Module\UrsPromotion\Models\UrsUserReferral::where('status', \App\Module\UrsPromotion\Models\UrsUserReferral::STATUS_VALID)->count();
+
+        $progressBar = $this->output->createProgressBar($totalUsers);
+        $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% - 当前用户ID: %message%');
+        $progressBar->setMessage('准备开始...');
         $progressBar->start();
-        
-        $result = $logic->rebuildAllRelationCache($batchSize);
-        
+
+        // 定义进度回调函数
+        $progressCallback = function($ursUserId, $processedCount, $totalUsers, $successCount, $failCount) use ($progressBar) {
+            $progressBar->setMessage("URS用户ID: {$ursUserId} (成功: {$successCount}, 失败: {$failCount})");
+            $progressBar->setProgress($processedCount);
+        };
+
+        $result = $logic->rebuildAllRelationCache($batchSize, $progressCallback);
+
         $progressBar->finish();
         $this->newLine();
         
@@ -118,4 +173,188 @@ class UrsRebuildRelationCacheCommand extends Command
         $this->info('所有URS用户关系缓存重建完成');
         return 0;
     }
+
+    /**
+     * 处理指定用户的缓存操作
+     *
+     * @param UrsRelationCacheLogic $logic
+     * @return int
+     */
+    private function handleSpecificUsers(UrsRelationCacheLogic $logic): int
+    {
+        // 获取用户ID列表
+        $userIds = $this->getSpecifiedUserIds();
+
+        if (empty($userIds)) {
+            $this->error('未指定有效的用户ID');
+            return 1;
+        }
+
+        $this->info('指定处理的URS用户ID: ' . implode(', ', $userIds));
+
+        // 验证用户ID是否存在
+        $validUserIds = $this->validateUserIds($userIds);
+        if (empty($validUserIds)) {
+            $this->error('所有指定的用户ID都无效');
+            return 1;
+        }
+
+        if (count($validUserIds) < count($userIds)) {
+            $invalidIds = array_diff($userIds, $validUserIds);
+            $this->warn('以下用户ID无效,将被跳过: ' . implode(', ', $invalidIds));
+        }
+
+        // 清除缓存操作
+        if ($this->option('clear')) {
+            return $this->clearSpecificUsersCache($logic, $validUserIds);
+        }
+
+        // 重建指定用户的缓存
+        return $this->rebuildSpecificUsersCache($logic, $validUserIds);
+    }
+
+    /**
+     * 获取指定的用户ID列表
+     *
+     * @return array
+     */
+    private function getSpecifiedUserIds(): array
+    {
+        $userIds = [];
+
+        // 处理单个用户ID
+        if ($this->option('user')) {
+            $userId = (int) $this->option('user');
+            if ($userId > 0) {
+                $userIds[] = $userId;
+            }
+        }
+
+        // 处理多个用户ID
+        if ($this->option('users')) {
+            $usersString = $this->option('users');
+            $userIdStrings = explode(',', $usersString);
+
+            foreach ($userIdStrings as $userIdString) {
+                $userId = (int) trim($userIdString);
+                if ($userId > 0 && !in_array($userId, $userIds)) {
+                    $userIds[] = $userId;
+                }
+            }
+        }
+
+        return $userIds;
+    }
+
+    /**
+     * 验证用户ID是否存在于URS推荐关系中
+     *
+     * @param array $userIds
+     * @return array
+     */
+    private function validateUserIds(array $userIds): array
+    {
+        return \App\Module\UrsPromotion\Models\UrsUserReferral::whereIn('urs_user_id', $userIds)
+            ->where('status', \App\Module\UrsPromotion\Models\UrsUserReferral::STATUS_VALID)
+            ->pluck('urs_user_id')
+            ->toArray();
+    }
+
+    /**
+     * 清除指定用户的缓存
+     *
+     * @param UrsRelationCacheLogic $logic
+     * @param array $userIds
+     * @return int
+     */
+    private function clearSpecificUsersCache(UrsRelationCacheLogic $logic, array $userIds): int
+    {
+        $this->info('开始清除指定用户的关系缓存...');
+
+        $progressBar = $this->output->createProgressBar(count($userIds));
+        $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% - 当前用户ID: %message%');
+        $progressBar->start();
+
+        $successCount = 0;
+        $failCount = 0;
+
+        foreach ($userIds as $userId) {
+            $progressBar->setMessage("URS用户ID: {$userId}");
+
+            if ($logic->clearUserRelationCache($userId)) {
+                $successCount++;
+            } else {
+                $failCount++;
+            }
+
+            $progressBar->advance();
+        }
+
+        $progressBar->finish();
+        $this->newLine();
+
+        $this->info('缓存清除完成:');
+        $this->table(['项目', '数量'], [
+            ['处理用户数', count($userIds)],
+            ['成功数', $successCount],
+            ['失败数', $failCount]
+        ]);
+
+        if ($failCount > 0) {
+            $this->warn('部分用户缓存清除失败,请检查日志');
+            return 1;
+        }
+
+        $this->info('指定用户关系缓存清除完成');
+        return 0;
+    }
+
+    /**
+     * 重建指定用户的缓存
+     *
+     * @param UrsRelationCacheLogic $logic
+     * @param array $userIds
+     * @return int
+     */
+    private function rebuildSpecificUsersCache(UrsRelationCacheLogic $logic, array $userIds): int
+    {
+        $this->info('开始重建指定用户的关系缓存...');
+
+        $progressBar = $this->output->createProgressBar(count($userIds));
+        $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% - 当前用户ID: %message%');
+        $progressBar->start();
+
+        $successCount = 0;
+        $failCount = 0;
+
+        foreach ($userIds as $userId) {
+            $progressBar->setMessage("URS用户ID: {$userId}");
+
+            if ($logic->generateUserRelationCache($userId)) {
+                $successCount++;
+            } else {
+                $failCount++;
+            }
+
+            $progressBar->advance();
+        }
+
+        $progressBar->finish();
+        $this->newLine();
+
+        $this->info('缓存重建完成:');
+        $this->table(['项目', '数量'], [
+            ['处理用户数', count($userIds)],
+            ['成功数', $successCount],
+            ['失败数', $failCount]
+        ]);
+
+        if ($failCount > 0) {
+            $this->warn('部分用户缓存生成失败,请检查日志');
+            return 1;
+        }
+
+        $this->info('指定用户关系缓存重建完成');
+        return 0;
+    }
 }

+ 17 - 3
app/Module/UrsPromotion/Commands/UrsUpdateActiveStatusCommand.php

@@ -16,9 +16,10 @@ class UrsUpdateActiveStatusCommand extends Command
     /**
      * 命令签名
      */
-    protected $signature = 'urs:update-active-status 
+    protected $signature = 'urs:update-active-status
                             {--limit=1000 : 每次处理的用户数量限制}
                             {--reset : 重置所有用户活跃状态}
+                            {--force : 强制检查所有用户,忽略last_activity_check时间限制}
                             {--dry-run : 仅显示统计信息,不执行更新}';
 
     /**
@@ -67,14 +68,24 @@ class UrsUpdateActiveStatusCommand extends Command
     protected function handleUpdate(): int
     {
         $limit = (int) $this->option('limit');
-        $this->info("开始批量更新用户活跃状态(限制:{$limit})...");
+        $force = $this->option('force');
+
+        if ($force) {
+            $this->info("开始强制更新所有用户活跃状态(限制:{$limit})...");
+        } else {
+            $this->info("开始批量更新用户活跃状态(限制:{$limit})...");
+        }
 
         // 显示更新前的统计信息
         $beforeStats = UrsActiveUserService::getDetailedActiveStats();
         $this->displayStats('更新前统计', $beforeStats);
 
         // 执行批量更新
-        $updateStats = UrsActiveUserService::batchUpdateActiveStatus($limit);
+        if ($force) {
+            $updateStats = UrsActiveUserService::forceUpdateActiveStatus($limit);
+        } else {
+            $updateStats = UrsActiveUserService::batchUpdateActiveStatus($limit);
+        }
         $this->displayUpdateStats($updateStats);
 
         // 显示更新后的统计信息
@@ -83,6 +94,7 @@ class UrsUpdateActiveStatusCommand extends Command
 
         // 记录执行日志
         Log::info('URS用户活跃状态更新任务完成', [
+            'force_mode' => $force,
             'before_stats' => $beforeStats,
             'update_stats' => $updateStats,
             'after_stats' => $afterStats
@@ -210,11 +222,13 @@ URS用户活跃状态更新命令
 选项:
   --limit=1000     每次处理的用户数量限制(默认1000)
   --reset          重置所有用户活跃状态
+  --force          强制检查所有用户,忽略last_activity_check时间限制
   --dry-run        仅显示统计信息,不执行更新
 
 示例:
   php artisan urs:update-active-status                    # 正常更新
   php artisan urs:update-active-status --limit=500        # 限制处理500个用户
+  php artisan urs:update-active-status --force            # 强制更新所有用户
   php artisan urs:update-active-status --dry-run          # 试运行模式
   php artisan urs:update-active-status --reset            # 重置所有状态
 

+ 60 - 4
app/Module/UrsPromotion/Commands/UrsUpdateTalentLevelCommand.php

@@ -102,16 +102,72 @@ class UrsUpdateTalentLevelCommand extends Command
             
             if ($talentDto) {
                 $this->info("✓ 成功更新用户 {$userId} 的达人等级");
+
+                // 基础信息表格
                 $this->table(['属性', '值'], [
                     ['URS用户ID', $talentDto->ursUserId],
                     ['达人等级', $talentDto->talentLevel],
                     ['等级名称', $talentDto->talentName],
-                    ['直推人数', $talentDto->directCount],
-                    ['间推人数', $talentDto->indirectCount],
-                    ['三推人数', $talentDto->thirdCount],
-                    ['团队总人数', $talentDto->promotionCount],
                     ['最后更新时间', $talentDto->lastLevelUpdateTime ?? '未更新']
                 ]);
+
+                // 团队统计表格
+                $this->line('');
+                $this->info('📊 团队统计数据:');
+                $this->table(['统计项', '总数', '活跃数', '活跃率'], [
+                    [
+                        '直推人数',
+                        $talentDto->directCount,
+                        $talentDto->activeDirectCount,
+                        $talentDto->directCount > 0 ? round($talentDto->activeDirectCount * 100 / $talentDto->directCount, 1) . '%' : '0%'
+                    ],
+                    ['间推人数', $talentDto->indirectCount, '-', '-'],
+                    ['三推人数', $talentDto->thirdCount, '-', '-'],
+                    [
+                        '团队总人数',
+                        $talentDto->promotionCount,
+                        $talentDto->activeTotalCount,
+                        $talentDto->promotionCount > 0 ? round($talentDto->activeTotalCount * 100 / $talentDto->promotionCount, 1) . '%' : '0%'
+                    ]
+                ]);
+
+                // 升级条件检查
+                if ($talentDto->nextConfig) {
+                    $this->line('');
+                    $this->info('🎯 下一等级升级条件:');
+                    $nextLevel = $talentDto->nextConfig;
+                    $directMet = $talentDto->directCount >= $nextLevel['direct_count_required'];
+                    $teamMet = $talentDto->promotionCount >= $nextLevel['promotion_count_required'];
+                    $activeDirectMet = $talentDto->activeDirectCount >= ($nextLevel['active_direct_required'] ?? 0);
+
+                    $this->table(['条件', '要求', '当前', '状态'], [
+                        [
+                            '直推人数',
+                            $nextLevel['direct_count_required'],
+                            $talentDto->directCount,
+                            $directMet ? '✅ 已满足' : '❌ 未满足'
+                        ],
+                        [
+                            '团队总人数',
+                            $nextLevel['promotion_count_required'],
+                            $talentDto->promotionCount,
+                            $teamMet ? '✅ 已满足' : '❌ 未满足'
+                        ],
+                        [
+                            '活跃直推',
+                            $nextLevel['active_direct_required'] ?? 0,
+                            $talentDto->activeDirectCount,
+                            $activeDirectMet ? '✅ 已满足' : '❌ 未满足'
+                        ]
+                    ]);
+
+                    $allMet = $directMet && $teamMet && $activeDirectMet;
+                    if ($allMet) {
+                        $this->info("🎉 恭喜!所有升级条件已满足,可升级到 {$nextLevel['name']}");
+                    } else {
+                        $this->warn("⚠️  还有条件未满足,无法升级到 {$nextLevel['name']}");
+                    }
+                }
             } else {
                 $this->error("✗ 用户 {$userId} 达人等级更新失败");
                 return 1;

+ 15 - 1
app/Module/UrsPromotion/Dtos/UrsUserTalentDto.php

@@ -77,15 +77,27 @@ class UrsUserTalentDto extends BaseDto
      */
     public ?array $nextConfig = null;
 
+    /**
+     * @var int 活跃直推人数
+     */
+    public int $activeDirectCount = 0;
+
+    /**
+     * @var int 活跃团队总人数
+     */
+    public int $activeTotalCount = 0;
+
     /**
      * 从模型创建DTO
      *
      * @param UrsUserTalent $model URS用户达人等级模型
      * @param array|null $currentConfig 当前等级配置(可选)
      * @param array|null $nextConfig 下一等级配置(可选)
+     * @param int $activeDirectCount 活跃直推人数(可选)
+     * @param int $activeTotalCount 活跃团队总人数(可选)
      * @return self
      */
-    public static function fromModel(UrsUserTalent $model, ?array $currentConfig = null, ?array $nextConfig = null): self
+    public static function fromModel(UrsUserTalent $model, ?array $currentConfig = null, ?array $nextConfig = null, int $activeDirectCount = 0, int $activeTotalCount = 0): self
     {
         $dto = new self();
         $dto->id = $model->id;
@@ -101,6 +113,8 @@ class UrsUserTalentDto extends BaseDto
         $dto->updatedAt = $model->updated_at ? $model->updated_at->toDateTimeString() : '';
         $dto->currentConfig = $currentConfig;
         $dto->nextConfig = $nextConfig;
+        $dto->activeDirectCount = $activeDirectCount;
+        $dto->activeTotalCount = $activeTotalCount;
 
         return $dto;
     }

+ 280 - 33
app/Module/UrsPromotion/Logics/UrsRelationCacheLogic.php

@@ -46,9 +46,42 @@ class UrsRelationCacheLogic
             $farmUserId = UrsUserMappingService::getFarmUserId($ursUserId);
             $farmReferrerId = UrsUserMappingService::getFarmUserId($ursReferrerId);
 
-            // 如果农场用户ID为0,跳过缓存生成
-            if ($farmUserId <= 0 || $farmReferrerId <= 0) {
-                Log::info("URS用户 {$ursUserId} 或推荐人 {$ursReferrerId} 未进入农场,跳过缓存生成");
+            // 如果当前用户未进入农场,无法生成缓存
+            if ($farmUserId <= 0) {
+                Log::info("URS用户 {$ursUserId} 未进入农场,跳过缓存生成");
+                return true;
+            }
+
+            // 如果推荐人未进入农场,创建占位缓存记录,避免断代问题
+            if ($farmReferrerId <= 0) {
+                Log::info("URS用户 {$ursUserId} 的推荐人 {$ursReferrerId} 未进入农场,创建占位缓存记录");
+
+                // 检查是否已存在该深度的缓存记录,避免重复插入
+                $existingCache = UrsUserRelationCache::where('urs_user_id', $ursUserId)
+                    ->where('urs_related_user_id', $ursReferrerId)
+                    ->where('depth', 1)
+                    ->first();
+
+                if (!$existingCache) {
+                    // 创建占位的直接关系缓存(农场用户ID为0)
+                    $directRelation = new UrsUserRelationCache();
+                    $directRelation->user_id = $farmUserId;
+                    $directRelation->related_user_id = 0; // 推荐人未进入农场,设为0
+                    $directRelation->urs_user_id = $ursUserId;
+                    $directRelation->urs_related_user_id = $ursReferrerId;
+                    $directRelation->level = UrsPromotionRelationLevel::DIRECT;
+                    $directRelation->path = '0'; // 占位路径
+                    $directRelation->urs_path = (string)$ursReferrerId;
+                    $directRelation->depth = 1;
+                    $directRelation->save();
+                } else {
+                    Log::debug("URS用户 {$ursUserId} 的占位缓存记录已存在,跳过创建");
+                }
+
+                // 尝试获取推荐人的上级关系(基于URS关系)
+                $this->generateIndirectRelationsFromUrsChain($ursUserId, $farmUserId, $ursReferrerId, 1, (string)$ursReferrerId, '0');
+
+                Log::info("URS用户 {$ursUserId} 占位缓存记录创建完成");
                 return true;
             }
 
@@ -64,31 +97,8 @@ class UrsRelationCacheLogic
             $directRelation->depth = 1;
             $directRelation->save();
 
-            // 获取推荐人的所有上级(限制在20代以内)
-            $upperRelations = UrsUserRelationCache::where('urs_user_id', $ursReferrerId)
-                ->where('depth', '<', UrsPromotionRelationLevel::getMaxLevel())
-                ->get();
-
-            // 创建间接关系缓存(最多20代)
-            foreach ($upperRelations as $upperRelation) {
-                $newDepth = $upperRelation->depth + 1;
-
-                // 限制最大深度为20代
-                if ($newDepth > UrsPromotionRelationLevel::getMaxLevel()) {
-                    continue;
-                }
-
-                $indirectRelation = new UrsUserRelationCache();
-                $indirectRelation->user_id = $farmUserId;
-                $indirectRelation->related_user_id = $upperRelation->related_user_id;
-                $indirectRelation->urs_user_id = $ursUserId;
-                $indirectRelation->urs_related_user_id = $upperRelation->urs_related_user_id;
-                $indirectRelation->level = UrsPromotionRelationLevel::getLevelByDepth($newDepth);
-                $indirectRelation->path = $farmReferrerId . ',' . $upperRelation->path;
-                $indirectRelation->urs_path = $ursReferrerId . ',' . $upperRelation->urs_path;
-                $indirectRelation->depth = $newDepth;
-                $indirectRelation->save();
-            }
+            // 基于URS推荐关系链直接生成所有间接关系(不依赖缓存)
+            $this->generateIndirectRelationsFromUrsChain($ursUserId, $farmUserId, $ursReferrerId, 1, (string)$ursReferrerId, (string)$farmReferrerId);
 
             // 清除Redis缓存
             Redis::del("urs_promotion:user:{$ursUserId}:all_referrers");
@@ -105,6 +115,108 @@ class UrsRelationCacheLogic
         }
     }
 
+    /**
+     * 基于URS关系链生成间接关系缓存
+     *
+     * 当推荐人未进入农场时,通过URS推荐关系链向上查找已进入农场的上级
+     *
+     * @param int $ursUserId 当前用户的URS用户ID
+     * @param int $farmUserId 当前用户的农场用户ID
+     * @param int $currentUrsReferrerId 当前层级的URS推荐人ID
+     * @param int $currentDepth 当前深度
+     * @param string $currentUrsPath 当前URS路径链
+     * @param string $currentFarmPath 当前农场路径链
+     * @return void
+     */
+    private function generateIndirectRelationsFromUrsChain(int $ursUserId, int $farmUserId, int $currentUrsReferrerId, int $currentDepth, string $currentUrsPath = '', string $currentFarmPath = ''): void
+    {
+        // 限制最大深度为20代
+        if ($currentDepth >= UrsPromotionRelationLevel::getMaxLevel()) {
+            return;
+        }
+
+        // 获取当前推荐人的推荐人
+        $upperReferral = UrsUserReferral::where('urs_user_id', $currentUrsReferrerId)
+            ->where('status', UrsUserReferral::STATUS_VALID)
+            ->first();
+
+        if (!$upperReferral) {
+            // 没有更上级的推荐人,结束递归
+            return;
+        }
+
+        $upperUrsReferrerId = $upperReferral->urs_referrer_id;
+        $upperFarmReferrerId = UrsUserMappingService::getFarmUserId($upperUrsReferrerId);
+        $newDepth = $currentDepth + 1;
+
+        // 构建新的路径链
+        $newUrsPath = empty($currentUrsPath) ? (string)$upperUrsReferrerId : $currentUrsPath . ',' . $upperUrsReferrerId;
+        $currentFarmReferrerId = UrsUserMappingService::getFarmUserId($currentUrsReferrerId);
+        $currentFarmPart = $currentFarmReferrerId > 0 ? (string)$currentFarmReferrerId : '0';
+        $upperFarmPart = $upperFarmReferrerId > 0 ? (string)$upperFarmReferrerId : '0';
+        $newFarmPath = empty($currentFarmPath) ? $upperFarmPart : $currentFarmPath . ',' . $upperFarmPart;
+
+        if ($upperFarmReferrerId > 0) {
+            // 检查是否已存在该深度的关系缓存
+            $existingCache = UrsUserRelationCache::where('urs_user_id', $ursUserId)
+                ->where('urs_related_user_id', $upperUrsReferrerId)
+                ->where('depth', $newDepth)
+                ->first();
+
+            if (!$existingCache) {
+                // 上级推荐人已进入农场,创建有效的关系缓存
+                $indirectRelation = new UrsUserRelationCache();
+                $indirectRelation->user_id = $farmUserId;
+                $indirectRelation->related_user_id = $upperFarmReferrerId;
+                $indirectRelation->urs_user_id = $ursUserId;
+                $indirectRelation->urs_related_user_id = $upperUrsReferrerId;
+                $indirectRelation->level = UrsPromotionRelationLevel::getLevelByDepth($newDepth);
+                $indirectRelation->path = $newFarmPath;
+                $indirectRelation->urs_path = $newUrsPath;
+                $indirectRelation->depth = $newDepth;
+                $indirectRelation->save();
+            }
+
+            Log::debug("为URS用户 {$ursUserId} 创建间接关系缓存", [
+                'upper_urs_referrer_id' => $upperUrsReferrerId,
+                'upper_farm_referrer_id' => $upperFarmReferrerId,
+                'depth' => $newDepth
+            ]);
+
+            // 继续向上递归查找,基于URS推荐关系链
+            $this->generateIndirectRelationsFromUrsChain($ursUserId, $farmUserId, $upperUrsReferrerId, $newDepth, $newUrsPath, $newFarmPath);
+        } else {
+            // 检查是否已存在该深度的占位缓存
+            $existingCache = UrsUserRelationCache::where('urs_user_id', $ursUserId)
+                ->where('urs_related_user_id', $upperUrsReferrerId)
+                ->where('depth', $newDepth)
+                ->first();
+
+            if (!$existingCache) {
+                // 上级推荐人也未进入农场,创建占位缓存并继续向上查找
+                $indirectRelation = new UrsUserRelationCache();
+                $indirectRelation->user_id = $farmUserId;
+                $indirectRelation->related_user_id = 0; // 占位
+                $indirectRelation->urs_user_id = $ursUserId;
+                $indirectRelation->urs_related_user_id = $upperUrsReferrerId;
+                $indirectRelation->level = UrsPromotionRelationLevel::getLevelByDepth($newDepth);
+                $indirectRelation->path = $newFarmPath;
+                $indirectRelation->urs_path = $newUrsPath;
+                $indirectRelation->depth = $newDepth;
+                $indirectRelation->save();
+            }
+
+            // 继续向上递归查找
+            $this->generateIndirectRelationsFromUrsChain($ursUserId, $farmUserId, $upperUrsReferrerId, $newDepth, $newUrsPath, $newFarmPath);
+        }
+    }
+
+
+
+
+
+
+
     /**
      * 清除用户的关系缓存
      *
@@ -140,9 +252,10 @@ class UrsRelationCacheLogic
      * 重建所有用户的关系缓存
      *
      * @param int $batchSize 批处理大小
+     * @param callable|null $progressCallback 进度回调函数
      * @return array 包含成功和失败的数量
      */
-    public function rebuildAllRelationCache(int $batchSize = 100): array
+    public function rebuildAllRelationCache(int $batchSize = 100, ?callable $progressCallback = null): array
     {
         try {
             // 清空关系缓存表
@@ -165,12 +278,21 @@ class UrsRelationCacheLogic
 
             $successCount = 0;
             $failCount = 0;
+            $totalUsers = count($ursUserIds);
+            $processedCount = 0;
 
             // 分批处理
             $chunks = array_chunk($ursUserIds, $batchSize);
 
             foreach ($chunks as $chunk) {
                 foreach ($chunk as $ursUserId) {
+                    $processedCount++;
+
+                    // 调用进度回调函数
+                    if ($progressCallback) {
+                        $progressCallback($ursUserId, $processedCount, $totalUsers, $successCount, $failCount);
+                    }
+
                     if ($this->generateUserRelationCache($ursUserId)) {
                         $successCount++;
                     } else {
@@ -248,9 +370,10 @@ class UrsRelationCacheLogic
     /**
      * 修复关系缓存问题
      *
+     * @param callable|null $progressCallback 进度回调函数
      * @return array 包含修复结果
      */
-    public function fixRelationCacheIssues(): array
+    public function fixRelationCacheIssues(?callable $progressCallback = null): array
     {
         try {
             // 检查完整性
@@ -268,8 +391,17 @@ class UrsRelationCacheLogic
                     ->pluck('urs_user_id')
                     ->toArray();
                 $missingUsers = array_diff($allUsers, $usersWithCache);
+                $totalMissing = count($missingUsers);
+                $processedMissing = 0;
 
                 foreach ($missingUsers as $ursUserId) {
+                    $processedMissing++;
+
+                    // 调用进度回调函数
+                    if ($progressCallback) {
+                        $progressCallback('missing', $ursUserId, $processedMissing, $totalMissing, $fixed['missing_users']);
+                    }
+
                     if ($this->generateUserRelationCache($ursUserId)) {
                         $fixed['missing_users']++;
                     }
@@ -284,8 +416,17 @@ class UrsRelationCacheLogic
                     LEFT JOIN kku_urs_promotion_user_referrals r ON c.urs_user_id = r.urs_user_id AND r.status = 1
                     WHERE r.urs_user_id IS NULL
                 ");
+                $totalOrphaned = count($orphanedCaches);
+                $processedOrphaned = 0;
 
                 foreach ($orphanedCaches as $cache) {
+                    $processedOrphaned++;
+
+                    // 调用进度回调函数
+                    if ($progressCallback) {
+                        $progressCallback('orphaned', $cache->urs_user_id, $processedOrphaned, $totalOrphaned, $fixed['orphaned_caches']);
+                    }
+
                     if ($this->clearUserRelationCache($cache->urs_user_id)) {
                         $fixed['orphaned_caches']++;
                     }
@@ -318,16 +459,31 @@ class UrsRelationCacheLogic
             UrsUserRelationCache::where('urs_user_id', $ursUserId)
                 ->update(['user_id' => $farmUserId]);
 
-            // 更新该用户作为推荐人的缓存记录
-            UrsUserRelationCache::where('urs_related_user_id', $ursUserId)
+            // 更新该用户作为推荐人的缓存记录(将占位的0更新为实际农场用户ID)
+            $updatedCount = UrsUserRelationCache::where('urs_related_user_id', $ursUserId)
+                ->where('related_user_id', 0) // 只更新占位记录
                 ->update(['related_user_id' => $farmUserId]);
 
+            if ($updatedCount > 0) {
+                Log::info("更新了 {$updatedCount} 条占位缓存记录", [
+                    'urs_user_id' => $ursUserId,
+                    'farm_user_id' => $farmUserId
+                ]);
+
+                // 更新路径中的占位符
+                $this->updatePlaceholderPaths($ursUserId, $farmUserId);
+            }
+
             // 重新生成该用户的关系缓存(确保路径正确)
             $this->generateUserRelationCache($ursUserId);
 
+            // 为该用户的所有下级重新生成缓存(修复断代问题)
+            $this->regenerateDownstreamCaches($ursUserId);
+
             Log::info("URS用户关系缓存中的农场用户ID更新成功", [
                 'urs_user_id' => $ursUserId,
-                'farm_user_id' => $farmUserId
+                'farm_user_id' => $farmUserId,
+                'updated_placeholder_count' => $updatedCount
             ]);
 
             return true;
@@ -340,4 +496,95 @@ class UrsRelationCacheLogic
             return false;
         }
     }
+
+    /**
+     * 更新路径中的占位符
+     *
+     * @param int $ursUserId URS用户ID
+     * @param int $farmUserId 农场用户ID
+     * @return void
+     */
+    private function updatePlaceholderPaths(int $ursUserId, int $farmUserId): void
+    {
+        try {
+            // 获取所有包含该用户的路径记录
+            $records = UrsUserRelationCache::where('urs_path', 'LIKE', "%{$ursUserId}%")->get();
+
+            foreach ($records as $record) {
+                $pathParts = explode(',', $record->path);
+                $ursPathParts = explode(',', $record->urs_path);
+
+                // 找到URS路径中对应的位置,更新农场用户路径
+                $ursIndex = array_search((string)$ursUserId, $ursPathParts);
+                if ($ursIndex !== false && isset($pathParts[$ursIndex]) && $pathParts[$ursIndex] === '0') {
+                    $pathParts[$ursIndex] = (string)$farmUserId;
+                    $record->path = implode(',', $pathParts);
+                    $record->save();
+                }
+            }
+
+            Log::debug("更新路径占位符完成", [
+                'urs_user_id' => $ursUserId,
+                'farm_user_id' => $farmUserId,
+                'updated_records' => $records->count()
+            ]);
+
+        } catch (\Exception $e) {
+            Log::warning("更新路径占位符失败", [
+                'urs_user_id' => $ursUserId,
+                'farm_user_id' => $farmUserId,
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
+
+    /**
+     * 为用户的所有下级重新生成缓存
+     *
+     * 当用户进入农场后,需要为其所有下级重新生成缓存以修复断代问题
+     *
+     * @param int $ursUserId URS用户ID
+     * @return void
+     */
+    private function regenerateDownstreamCaches(int $ursUserId): void
+    {
+        try {
+            // 获取该用户的所有直接下级
+            $directSubordinates = UrsUserReferral::where('urs_referrer_id', $ursUserId)
+                ->where('status', UrsUserReferral::STATUS_VALID)
+                ->pluck('urs_user_id')
+                ->toArray();
+
+            if (empty($directSubordinates)) {
+                return;
+            }
+
+            Log::info("开始为用户的下级重新生成缓存", [
+                'urs_user_id' => $ursUserId,
+                'subordinate_count' => count($directSubordinates)
+            ]);
+
+            foreach ($directSubordinates as $subordinateUrsUserId) {
+                // 检查下级是否已进入农场
+                $subordinateFarmUserId = UrsUserMappingService::getFarmUserId($subordinateUrsUserId);
+                if ($subordinateFarmUserId > 0) {
+                    // 重新生成下级的关系缓存
+                    $this->generateUserRelationCache($subordinateUrsUserId);
+
+                    // 递归处理下级的下级
+                    $this->regenerateDownstreamCaches($subordinateUrsUserId);
+                }
+            }
+
+            Log::info("用户下级缓存重新生成完成", [
+                'urs_user_id' => $ursUserId
+            ]);
+
+        } catch (\Exception $e) {
+            Log::error("重新生成下级缓存失败", [
+                'urs_user_id' => $ursUserId,
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
 }

+ 4 - 1
app/Module/UrsPromotion/Models/UrsUserMapping.php

@@ -164,6 +164,9 @@ class UrsUserMapping extends ModelCore
                 'user_id' => $userId,
                 'mapping_time' => now(),
                 'status' => self::STATUS_VALID,
+                'is_active' => self::ACTIVE_YES, // 新用户默认为活跃状态
+                'last_activity_check' => now(), // 设置初始检查时间
+                'active_days_count' => 1, // 新用户默认活跃天数为1
             ]
         );
     }
@@ -286,7 +289,7 @@ class UrsUserMapping extends ModelCore
                 $query->whereNull('last_activity_check')
                     ->orWhere('last_activity_check', '<', now()->subDay());
             })
-            ->with('user')
+            ->with(['user', 'user.info']) // 预加载用户和用户信息关联
             ->limit($limit)
             ->get();
     }

+ 4 - 3
app/Module/UrsPromotion/Models/UrsUserRelationCache.php

@@ -3,13 +3,14 @@
 namespace App\Module\UrsPromotion\Models;
 
 use App\Module\UrsPromotion\Enums\UrsPromotionRelationLevel;
+use App\Module\User\Models\User;
 use Illuminate\Database\Eloquent\Relations\BelongsTo;
 use UCore\ModelCore;
 
 /**
  * URS用户关系缓存模型
- * 
- * field start 
+ *
+ * field start
  * @property  int  $id  主键ID
  * @property  int  $user_id  农场用户ID
  * @property  int  $related_user_id  关联农场用户ID(上级)
@@ -22,7 +23,7 @@ use UCore\ModelCore;
  * @property  \Carbon\Carbon  $created_at  创建时间
  * @property  \Carbon\Carbon  $updated_at  更新时间
  * field end
- * 
+ *
  * @property-read User $user 农场用户
  * @property-read User $relatedUser 关联农场用户(上级)
  */

+ 9 - 0
app/Module/UrsPromotion/Providers/UrsPromotionServiceProvider.php

@@ -50,6 +50,8 @@ class UrsPromotionServiceProvider extends ServiceProvider
                 \App\Module\UrsPromotion\Commands\UrsTestRelationCacheCommand::class,
                 \App\Module\UrsPromotion\Commands\UrsReferralSyncCommand::class,
                 \App\Module\UrsPromotion\Commands\UrsUpdateTalentLevelCommand::class,
+                \App\Module\UrsPromotion\Commands\TestRelationCacheFixCommand::class,
+                \App\Module\UrsPromotion\Commands\UrsUpdateActiveStatusCommand::class,
             ]);
         }
     }
@@ -81,6 +83,13 @@ class UrsPromotionServiceProvider extends ServiceProvider
                 ->description('URS合伙人分红 - 顶级达人享受手续费分红')
                 ->withoutOverlapping() // 防止重复执行
                 ->runInBackground(); // 后台运行
+
+            // 每天01:05执行用户活跃状态更新
+            $schedule->command('urs:update-active-status')
+                ->dailyAt('01:05')
+                ->description('URS用户活跃状态更新 - 基于最近15天活动时间更新用户活跃状态')
+                ->withoutOverlapping() // 防止重复执行
+                ->runInBackground(); // 后台运行
         });
     }
 

+ 125 - 6
app/Module/UrsPromotion/Services/UrsActiveUserService.php

@@ -88,7 +88,7 @@ class UrsActiveUserService
         ];
 
         try {
-            // 获取需要检查的用户
+            // 获取需要检查的用户(预加载用户和用户信息关联)
             $mappings = UrsUserMapping::getUsersNeedActivityCheck($limit);
             $stats['total_processed'] = $mappings->count();
 
@@ -143,6 +143,82 @@ class UrsActiveUserService
         return $stats;
     }
 
+    /**
+     * 强制更新用户活跃状态(忽略last_activity_check时间限制)
+     *
+     * @param int $limit 每次处理的用户数量限制
+     * @return array 更新结果统计
+     */
+    public static function forceUpdateActiveStatus(int $limit = 1000): array
+    {
+        $stats = [
+            'total_processed' => 0,
+            'successful_updates' => 0,
+            'failed_updates' => 0,
+            'active_users' => 0,
+            'inactive_users' => 0,
+        ];
+
+        try {
+            // 获取所有有效的用户映射(忽略last_activity_check限制)
+            $mappings = UrsUserMapping::where('status', UrsUserMapping::STATUS_VALID)
+                ->with(['user', 'user.info'])
+                ->limit($limit)
+                ->get();
+
+            $stats['total_processed'] = $mappings->count();
+
+            Log::info('开始强制更新URS用户活跃状态', [
+                'total_users' => $stats['total_processed'],
+                'limit' => $limit
+            ]);
+
+            foreach ($mappings as $mapping) {
+                try {
+                    if (!$mapping->user) {
+                        $stats['failed_updates']++;
+                        continue;
+                    }
+
+                    // 检查用户活跃状态
+                    $isActive = self::checkUserActivity($mapping->user);
+                    $activeDaysCount = self::calculateActiveDaysCount($mapping->user);
+
+                    // 更新活跃状态
+                    $mapping->update([
+                        'is_active' => $isActive ? UrsUserMapping::ACTIVE_YES : UrsUserMapping::ACTIVE_NO,
+                        'last_activity_check' => now(),
+                        'active_days_count' => $activeDaysCount,
+                    ]);
+
+                    $stats['successful_updates']++;
+                    if ($isActive) {
+                        $stats['active_users']++;
+                    } else {
+                        $stats['inactive_users']++;
+                    }
+
+                } catch (\Exception $e) {
+                    $stats['failed_updates']++;
+                    Log::error('单个用户活跃状态强制更新失败', [
+                        'urs_user_id' => $mapping->urs_user_id,
+                        'error' => $e->getMessage()
+                    ]);
+                }
+            }
+
+            Log::info('强制更新URS用户活跃状态完成', $stats);
+
+        } catch (\Exception $e) {
+            Log::error('强制更新URS用户活跃状态失败', [
+                'error' => $e->getMessage(),
+                'stats' => $stats
+            ]);
+        }
+
+        return $stats;
+    }
+
     /**
      * 检查用户活跃状态
      *
@@ -151,12 +227,14 @@ class UrsActiveUserService
      */
     public static function checkUserActivity(User $user): bool
     {
-        if (!$user->last_activity_time) {
+        // 获取用户信息中的活动时间
+        $userInfo = $user->info;
+        if (!$userInfo || !$userInfo->last_activity_time) {
             return false;
         }
 
         $threshold = Carbon::now()->subDays(self::ACTIVE_DAYS_THRESHOLD);
-        return $user->last_activity_time >= $threshold;
+        return $userInfo->last_activity_time >= $threshold;
     }
 
     /**
@@ -167,12 +245,14 @@ class UrsActiveUserService
      */
     public static function calculateActiveDaysCount(User $user): int
     {
-        if (!$user->last_activity_time) {
+        // 获取用户信息中的活动时间
+        $userInfo = $user->info;
+        if (!$userInfo || !$userInfo->last_activity_time) {
             return 0;
         }
 
-        $daysSinceLastActivity = Carbon::now()->diffInDays($user->last_activity_time);
-        
+        $daysSinceLastActivity = Carbon::now()->diffInDays($userInfo->last_activity_time);
+
         // 如果在活跃期内,返回1,否则返回0
         return $daysSinceLastActivity <= self::ACTIVE_DAYS_THRESHOLD ? 1 : 0;
     }
@@ -242,6 +322,45 @@ class UrsActiveUserService
         ];
     }
 
+    /**
+     * 获取指定URS用户的活跃直推成员(仅直推层级)
+     *
+     * 针对达人等级升级条件优化,只统计直推活跃用户
+     * 相比getActiveTeamMembers方法,此方法只关注直推层级,性能更优
+     *
+     * @param int $ursUserId URS用户ID
+     * @return array 返回活跃直推统计信息
+     */
+    public static function getActiveDirectMembers(int $ursUserId): array
+    {
+        // 只获取直推成员(第1层级)
+        $directMembers = UrsReferralService::getDirectReferrals($ursUserId);
+
+        if (empty($directMembers)) {
+            return [
+                'active_direct_count' => 0,
+                'active_direct_members' => []
+            ];
+        }
+
+        // 获取活跃的直推成员
+        $activeDirectIds = UrsUserMapping::getActiveUrsUserIds($directMembers);
+
+        // 构建活跃直推成员详情
+        $activeDirectMembers = [];
+        foreach ($activeDirectIds as $memberId) {
+            $activeDirectMembers[] = [
+                'urs_user_id' => $memberId,
+                'level' => 1 // 直推层级
+            ];
+        }
+
+        return [
+            'active_direct_count' => count($activeDirectIds),
+            'active_direct_members' => $activeDirectMembers
+        ];
+    }
+
     /**
      * 获取活跃用户详细统计
      *

+ 54 - 5
app/Module/UrsPromotion/Services/UrsReferralService.php

@@ -90,6 +90,23 @@ class UrsReferralService
         return self::getTeamMembersFromCache($ursUserId, $maxLevels);
     }
 
+    /**
+     * 获取团队成员数量
+     * @param int $ursUserId
+     * @param int|null $maxLevels
+     * @return array
+     */
+    public static function getTeamMNumber(int $ursUserId, ?int $maxLevels = null): array
+    {
+        // 如果没有指定层级数,使用配置的团队统计深度
+        if ($maxLevels === null) {
+            $maxLevels = UrsPromotionRelationLevel::getTeamStatsDepth();
+        }
+
+        // 优先使用关系缓存表进行查询,性能更好
+        return self::getTeamMNumberFromCache($ursUserId, $maxLevels);
+    }
+
     /**
      * 从关系缓存表获取团队成员(高性能版本)
      *
@@ -117,6 +134,37 @@ class UrsReferralService
         return $team;
     }
 
+    /**
+     * 获取团队成员人数/层级
+     * @param int $ursUserId
+     * @param int $maxLevels
+     * @return array [代数=>人数]
+     */
+    private static function getTeamMNumberFromCache(int $ursUserId, int $maxLevels): array
+    {
+
+        // 使用关系缓存表查询所有下级关系
+        $relations = \App\Module\UrsPromotion\Models\UrsUserRelationCache::where('urs_related_user_id', $ursUserId)
+            ->where('depth', '<=', $maxLevels)
+            ->where('user_id','>',0)
+            ->orderBy('depth')
+            ->groupBy('depth')
+            ->pluck(
+                DB::raw('count(*) as total'),
+                'depth'
+                    );
+
+        $team = [];
+        $totle = 0;
+        foreach ($relations as $level=>$number) {
+            $team[$level] = $number;
+            $totle += $number;
+        }
+        $team['total'] = $totle;
+//        dd($relations);
+        return $team;
+    }
+
     /**
      * 获取用户的团队成员(递归查询版本,作为备用方案)
      *
@@ -150,17 +198,18 @@ class UrsReferralService
 
 
     /**
-     * 获取三级内活跃用户数量
+     * 获取活跃用户数量
      *
      * @param int $userId 农场用户ID
+     * @param int $level 等级
      * @return int 活跃用户数量(映射关系有效且用户活跃)
      */
-    public static function getTeamANumber($userId): int
+    public static function getTeamANumber($userId,$level = 3): int
     {
 
         // 通过关系缓存表获取三代内所有的URS用户ID
         $ursUserIds = \App\Module\UrsPromotion\Models\UrsUserRelationCache::where('related_user_id', $userId)
-            ->where('depth', '<=', 3) // 只统计前3级
+            ->where('depth', '<=', $level) // 只统计前3级
             ->pluck('urs_user_id')
             ->toArray();
 
@@ -239,9 +288,9 @@ class UrsReferralService
      */
     public static function getReferralStats(int $ursUserId): array
     {
-        $teamMembers = self::getTeamMembers($ursUserId);
+        $teamMembers = self::getTeamMembers($ursUserId,3);
         $referralChain = self::getReferralChain($ursUserId);
-
+        dump($teamMembers);
         return [
             'urs_user_id' => $ursUserId,
             'referrer_id' => self::getReferrer($ursUserId),

+ 25 - 14
app/Module/UrsPromotion/Services/UrsTalentService.php

@@ -27,17 +27,20 @@ class UrsTalentService
             DB::beginTransaction();
             $ursUserId= UrsUserMappingService::getMappingUrsUserId($userId);
             // 获取用户的团队统计
-            $stats = UrsReferralService::getReferralStats($ursUserId);
+            $stats = UrsReferralService::getTeamMNumber($ursUserId);
 
-            // 获取活跃团队成员统计
-            $activeStats = UrsActiveUserService::getActiveTeamMembers($ursUserId);
+            // 获取活跃直推成员统计(优化性能,只获取直推活跃数据)
+            $activeDirectStats = UrsActiveUserService::getActiveDirectMembers($ursUserId);
+
+            // 为了显示完整信息,也获取活跃团队总数
+            $activeTeamStats = UrsActiveUserService::getActiveTeamMembers($ursUserId);
 
             // 计算应该的达人等级
             $newLevel = self::calculateTalentLevel(
-                $stats['direct_count'],
-                $stats['total_team_count'],
-                $activeStats['active_direct_count'],
-                $activeStats['active_total_count']
+                $stats[1],
+                $stats['total'],
+                $activeDirectStats['active_direct_count'],
+                0 // 团队活跃数暂时不需要,传0
             );
 
             // 获取或创建达人记录
@@ -56,10 +59,10 @@ class UrsTalentService
 
             // 更新团队统计数据(使用模型方法确保逻辑一致性)
             $talent->updateTeamStats(
-                $stats['direct_count'],
-                $stats['indirect_count'],
-                $stats['third_count'],
-                $stats['total_team_count'] // 20代总人数
+                $stats[1],
+                $stats[2],
+                $stats[3],
+                $stats['total'] // 20代总人数
             );
 
             // 更新达人等级
@@ -76,8 +79,8 @@ class UrsTalentService
                     'user_id' => $ursUserId,
                     'old_level' => $oldLevel,
                     'new_level' => $newLevel,
-                    'direct_count' => $stats['direct_count'],
-                    'total_team_count' => $stats['total_team_count']
+                    'direct_count' => $stats[1],
+                    'total_team_count' => $stats['total'],
                 ]);
             }
 
@@ -96,9 +99,17 @@ class UrsTalentService
                 'name' => $nextConfig->name,
                 'direct_count_required' => $nextConfig->direct_count_required,
                 'promotion_count_required' => $nextConfig->promotion_count_required,
+                'active_direct_required' => $nextConfig->active_direct_required,
+                'active_count_required' => $nextConfig->active_count_required,
             ] : null;
 
-            return UrsUserTalentDto::fromModel($talent, $currentConfig, $nextConfigArray);
+            return UrsUserTalentDto::fromModel(
+                $talent,
+                $currentConfig,
+                $nextConfigArray,
+                $activeDirectStats['active_direct_count'],
+                $activeTeamStats['active_total_count']
+            );
 
         } catch (\Exception $e) {
             DB::rollBack();

+ 2 - 2
docker-compose.dev.yml

@@ -1,5 +1,5 @@
 networks:
-    aaaaa:
+    ggggg:
         external: true
 services:
   dev:
@@ -17,4 +17,4 @@ services:
     ports:
        - 80
     networks:
-        - aaaaa
+        - ggggg

+ 2 - 2
docker-compose.devprod.yml

@@ -1,5 +1,5 @@
 networks:
-    aaaaa:
+    ggggg:
         external: true
 services:
   dev:
@@ -17,4 +17,4 @@ services:
     ports:
        - 80
     networks:
-        - aaaaa
+        - ggggg

+ 28 - 22
tests/Unit/AppGame/Handler/Promotion/TodayStatsLogicTest.php

@@ -4,6 +4,7 @@ namespace Tests\Unit\AppGame\Handler\Promotion;
 
 use Tests\TestCase;
 use App\Module\UrsPromotion\Models\UrsUserRelationCache;
+use App\Module\UrsPromotion\Models\UrsUserMapping;
 use App\Module\UrsPromotion\Services\UrsUserMappingService;
 use Illuminate\Support\Facades\DB;
 use Carbon\Carbon;
@@ -16,52 +17,57 @@ use Carbon\Carbon;
 class TodayStatsLogicTest extends TestCase
 {
     /**
-     * 测试优化后的查询逻辑
+     * 测试优化后的查询逻辑(基于mapping_time)
      */
-    public function test_optimized_query_logic()
+    public function test_optimized_query_logic_with_mapping_time()
     {
         // 模拟查询逻辑
         $farmUserId = 20001;
-        
-        // 构建优化后的查询SQL
-        $expectedSql = "select COUNT(CASE WHEN depth = 1 THEN 1 END) as direct_new_count, COUNT(CASE WHEN depth <= 3 THEN 1 END) as team_new_count from `kku_urs_promotion_user_relation_cache` where `related_user_id` = ? and date(`created_at`) = ?";
-        
+
+        // 构建基于mapping_time的查询SQL
+        $expectedSql = "select COUNT(CASE WHEN kku_urs_promotion_user_relation_cache.depth = 1 THEN 1 END) as direct_new_count, COUNT(CASE WHEN kku_urs_promotion_user_relation_cache.depth <= 3 THEN 1 END) as team_new_count from `kku_urs_promotion_user_relation_cache` inner join `kku_urs_promotion_user_mappings` on `kku_kku_urs_promotion_user_relation_cache`.`urs_user_id` = `kku_urs_promotion_user_mappings`.`urs_user_id` where `related_user_id` = ? and `kku_urs_promotion_user_mappings`.`status` = ? and date(`kku_urs_promotion_user_mappings`.`mapping_time`) = ?";
+
         // 验证查询构建逻辑
         $query = UrsUserRelationCache::where('related_user_id', $farmUserId)
-            ->whereDate('created_at', today())
+            ->join('urs_promotion_user_mappings', 'kku_urs_promotion_user_relation_cache.urs_user_id', '=', 'urs_promotion_user_mappings.urs_user_id')
+            ->where('urs_promotion_user_mappings.status', UrsUserMapping::STATUS_VALID)
+            ->whereDate('urs_promotion_user_mappings.mapping_time', today())
             ->selectRaw('
-                COUNT(CASE WHEN depth = 1 THEN 1 END) as direct_new_count,
-                COUNT(CASE WHEN depth <= 3 THEN 1 END) as team_new_count
+                COUNT(CASE WHEN kku_urs_promotion_user_relation_cache.depth = 1 THEN 1 END) as direct_new_count,
+                COUNT(CASE WHEN kku_urs_promotion_user_relation_cache.depth <= 3 THEN 1 END) as team_new_count
             ');
-            
+
         $actualSql = $query->toSql();
-        
+
         // 清理SQL中的多余空格和换行
         $cleanExpectedSql = preg_replace('/\s+/', ' ', trim($expectedSql));
         $cleanActualSql = preg_replace('/\s+/', ' ', trim($actualSql));
-        
+
         $this->assertEquals($cleanExpectedSql, $cleanActualSql);
     }
 
     /**
-     * 测试查询参数绑定
+     * 测试查询参数绑定(基于mapping_time)
      */
-    public function test_query_bindings()
+    public function test_query_bindings_with_mapping_time()
     {
         $farmUserId = 20001;
-        
+
         $query = UrsUserRelationCache::where('related_user_id', $farmUserId)
-            ->whereDate('created_at', today())
+            ->join('urs_promotion_user_mappings', 'kku_urs_promotion_user_relation_cache.urs_user_id', '=', 'urs_promotion_user_mappings.urs_user_id')
+            ->where('urs_promotion_user_mappings.status', UrsUserMapping::STATUS_VALID)
+            ->whereDate('urs_promotion_user_mappings.mapping_time', today())
             ->selectRaw('
-                COUNT(CASE WHEN depth = 1 THEN 1 END) as direct_new_count,
-                COUNT(CASE WHEN depth <= 3 THEN 1 END) as team_new_count
+                COUNT(CASE WHEN kku_urs_promotion_user_relation_cache.depth = 1 THEN 1 END) as direct_new_count,
+                COUNT(CASE WHEN kku_urs_promotion_user_relation_cache.depth <= 3 THEN 1 END) as team_new_count
             ');
-            
+
         $bindings = $query->getBindings();
-        
-        // 验证绑定参数
+
+        // 验证绑定参数:related_user_id, status, mapping_time
         $this->assertEquals($farmUserId, $bindings[0]);
-        $this->assertEquals(today()->format('Y-m-d'), $bindings[1]);
+        $this->assertEquals(UrsUserMapping::STATUS_VALID, $bindings[1]);
+        $this->assertEquals(today()->format('Y-m-d'), $bindings[2]);
     }
 
     /**