Quellcode durchsuchen

单独导入--加载组织信息

wangxx vor 5 Tagen
Ursprung
Commit
3bae8c3024

+ 135 - 21
airport-admin/src/main/java/com/sundot/airport/web/controller/ledger/LedgerImportController.java

@@ -1,7 +1,6 @@
1 1
 package com.sundot.airport.web.controller.ledger;
2 2
 
3 3
 import cn.hutool.core.collection.CollUtil;
4
-import cn.hutool.core.date.DateUtil;
5 4
 import com.sundot.airport.common.dto.ImportResultVO;
6 5
 import com.sundot.airport.common.utils.poi.ExcelUtil;
7 6
 import com.sundot.airport.common.annotation.Log;
@@ -9,10 +8,10 @@ import com.sundot.airport.common.core.controller.BaseController;
9 8
 import com.sundot.airport.common.core.domain.AjaxResult;
10 9
 import com.sundot.airport.common.enums.BusinessType;
11 10
 import com.sundot.airport.common.utils.DateUtils;
12
-import com.sundot.airport.common.utils.poi.ExcelUtil;
13 11
 import com.sundot.airport.ledger.domain.*;
14 12
 import com.sundot.airport.ledger.service.*;
15 13
 import com.sundot.airport.ledger.service.ILedgerCombinedImportService;
14
+import com.sundot.airport.ledger.util.OrgInfoHelper;
16 15
 import com.sundot.airport.ledger.domain.LedgerDailyTraining;
17 16
 import com.sundot.airport.ledger.domain.LedgerLeaderDuty;
18 17
 import com.sundot.airport.ledger.domain.LedgerHealthSoldier;
@@ -62,6 +61,9 @@ public class LedgerImportController extends BaseController {
62 61
     @Autowired private ILedgerBannerLetterService bannerLetterService;
63 62
     @Autowired private ILedgerQualificationLevelService qualificationLevelService;
64 63
 
64
+    @Autowired
65
+    private OrgInfoHelper orgInfoHelper;
66
+
65 67
 
66 68
     private String generateBatchNo() {
67 69
         return DateUtils.dateTimeNow("yyyyMMddHHmmss") + UUID.randomUUID().toString().substring(0, 6).toUpperCase();
@@ -81,6 +83,13 @@ public class LedgerImportController extends BaseController {
81 83
         if (list == null || list.isEmpty()) {
82 84
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
83 85
         }
86
+        // 批量预加载组织信息
87
+        orgInfoHelper.batchFillOrgAndLocationInfo(list,
88
+                LedgerSupervisionProblem::getInspectedName,
89
+                LedgerSupervisionProblem::getPosition,
90
+                LedgerSupervisionProblem::getLocation,
91
+                LedgerSupervisionProblem::getChannelNo);
92
+
84 93
         list.forEach(item -> {
85 94
             item.setImportBatch(batchNo);
86 95
             item.setSourceType("1");
@@ -127,6 +136,9 @@ public class LedgerImportController extends BaseController {
127 136
         if (list == null || list.isEmpty()) {
128 137
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
129 138
         }
139
+        // 批量预加载组织信息(性能优化)
140
+        orgInfoHelper.batchFillOrgInfo(list, LedgerPatrolInspection::getInspectorName);
141
+
130 142
         list.forEach(item -> {
131 143
             item.setImportBatch(batchNo);
132 144
             item.setSourceType("1");
@@ -150,6 +162,15 @@ public class LedgerImportController extends BaseController {
150 162
         if (list == null || list.isEmpty()) {
151 163
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
152 164
         }
165
+        // 批量预加载组织信息和岗位/区域/通道
166
+        orgInfoHelper.batchFillOrgAndLocationInfo(
167
+                list,
168
+                LedgerRealtimeInterception::getInspectorName,
169
+                LedgerRealtimeInterception::getPosition,
170
+                LedgerRealtimeInterception::getLocation,
171
+                LedgerRealtimeInterception::getChannelNo
172
+        );
173
+
153 174
         list.forEach(item -> {
154 175
             item.setImportBatch(batchNo);
155 176
             item.setSourceType("1");
@@ -196,6 +217,15 @@ public class LedgerImportController extends BaseController {
196 217
         if (list == null || list.isEmpty()) {
197 218
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
198 219
         }
220
+        // 批量预加载组织信息和岗位/区域/通道
221
+        orgInfoHelper.batchFillOrgAndLocationInfo(
222
+                list,
223
+                LedgerServicePatrol::getInspectedName,
224
+                LedgerServicePatrol::getPosition,
225
+                LedgerServicePatrol::getLocation,
226
+                LedgerServicePatrol::getChannelNo
227
+        );
228
+
199 229
         list.forEach(item -> {
200 230
             item.setImportBatch(batchNo);
201 231
             item.setSourceType("1");
@@ -242,6 +272,9 @@ public class LedgerImportController extends BaseController {
242 272
         if (list == null || list.isEmpty()) {
243 273
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
244 274
         }
275
+        // 批量预加载组织信息(性能优化)
276
+        orgInfoHelper.batchFillOrgInfo(list, LedgerComplaint::getResponsibleName);
277
+
245 278
         list.forEach(item -> {
246 279
             item.setImportBatch(batchNo);
247 280
             item.setSourceType("1");
@@ -250,7 +283,6 @@ public class LedgerImportController extends BaseController {
250 283
         complaintService.batchInsert(list);
251 284
         return AjaxResult.success("导入成功,共" + list.size() + "条");
252 285
     }
253
-
254 286
     @PreAuthorize("@ss.hasPermi('ledger:import:securityTest')")
255 287
     @Log(title = "台账导入-安保测试", businessType = BusinessType.IMPORT)
256 288
     @PostMapping("/securityTest")
@@ -265,6 +297,15 @@ public class LedgerImportController extends BaseController {
265 297
         if (list == null || list.isEmpty()) {
266 298
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
267 299
         }
300
+        // 批量预加载组织信息和岗位/区域/通道
301
+        orgInfoHelper.batchFillOrgAndLocationInfo(
302
+                list,
303
+                LedgerSecurityTest::getTestedName,  // 被测试人员
304
+                LedgerSecurityTest::getTestType,    // 被测试岗位
305
+                LedgerSecurityTest::getRegion,      // 测试区域
306
+                LedgerSecurityTest::getChannel      // 测试通道
307
+        );
308
+
268 309
         list.forEach(item -> {
269 310
             item.setImportBatch(batchNo);
270 311
             item.setSourceType("1");
@@ -311,6 +352,14 @@ public class LedgerImportController extends BaseController {
311 352
         if (list == null || list.isEmpty()) {
312 353
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
313 354
         }
355
+        // 仅根据组织名称填充组织ID(无人员姓名)
356
+        orgInfoHelper.batchFillOrgIdByNameOnly(
357
+                list,
358
+                LedgerChannelPassRate::getDeptName,   // 部门名称
359
+                LedgerChannelPassRate::getTeamName,   // 队室名称
360
+                LedgerChannelPassRate::getGroupName   // 小组名称
361
+        );
362
+
314 363
         list.forEach(item -> {
315 364
             item.setImportBatch(batchNo);
316 365
             item.setSourceType("1");
@@ -334,6 +383,14 @@ public class LedgerImportController extends BaseController {
334 383
         if (list == null || list.isEmpty()) {
335 384
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
336 385
         }
386
+        // 批量预加载组织信息和岗位/区域/通道
387
+        orgInfoHelper.batchFillOrgAndLocationInfo(
388
+                list,
389
+                LedgerUnsafeEvent::getResponsibleName,
390
+                LedgerUnsafeEvent::getPosition,
391
+                LedgerUnsafeEvent::getArea,
392
+                LedgerUnsafeEvent::getChannelNo
393
+        );
337 394
         list.forEach(item -> {
338 395
             item.setImportBatch(batchNo);
339 396
             item.setSourceType("1");
@@ -380,19 +437,29 @@ public class LedgerImportController extends BaseController {
380 437
         if (list == null || list.isEmpty()) {
381 438
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
382 439
         }
440
+        // 批量预加载组织信息和岗位/区域/通道
441
+        orgInfoHelper.batchFillOrgAndLocationInfo(
442
+                list,
443
+                LedgerSeizureStats::getInspectorName,
444
+                LedgerSeizureStats::getPosition,
445
+                LedgerSeizureStats::getWorkArea,
446
+                null
447
+        );
383 448
         list.forEach(item -> {
384 449
             item.setImportBatch(batchNo);
385 450
             item.setSourceType("1");
386 451
             item.setCreateBy(getUsername());
387
-            //合并 recordDate 和 seizureTime 字段
388
-            java.util.Calendar cal = java.util.Calendar.getInstance();
389
-            cal.setTime(item.getRecordDate());
390
-            java.util.Calendar timeCal = java.util.Calendar.getInstance();
391
-            timeCal.setTime(item.getSeizureTime());
392
-            cal.set(java.util.Calendar.HOUR_OF_DAY, timeCal.get(java.util.Calendar.HOUR_OF_DAY));
393
-            cal.set(java.util.Calendar.MINUTE, timeCal.get(java.util.Calendar.MINUTE));
394
-            cal.set(java.util.Calendar.SECOND, timeCal.get(java.util.Calendar.SECOND));
395
-            item.setRecordDate(cal.getTime());
452
+            // 合并 recordDate 和 seizureTime 字段
453
+            if (item.getRecordDate() != null && item.getSeizureTime() != null) {
454
+                java.util.Calendar cal = java.util.Calendar.getInstance();
455
+                cal.setTime(item.getRecordDate());
456
+                java.util.Calendar timeCal = java.util.Calendar.getInstance();
457
+                timeCal.setTime(item.getSeizureTime());
458
+                cal.set(java.util.Calendar.HOUR_OF_DAY, timeCal.get(java.util.Calendar.HOUR_OF_DAY));
459
+                cal.set(java.util.Calendar.MINUTE, timeCal.get(java.util.Calendar.MINUTE));
460
+                cal.set(java.util.Calendar.SECOND, timeCal.get(java.util.Calendar.SECOND));
461
+                item.setRecordDate(cal.getTime());
462
+            }
396 463
         });
397 464
         if (CollUtil.isNotEmpty(list)) {
398 465
             List<LedgerSeizureStats> existList = seizureStatsService.selectList(new LedgerSeizureStats());
@@ -429,7 +496,7 @@ public class LedgerImportController extends BaseController {
429 496
         ExcelUtil<LedgerTerminalBonus> util = new ExcelUtil<>(LedgerTerminalBonus.class);
430 497
         // titleNum=1 表示跳过第1行标题,从第2行读取表头
431 498
         List<LedgerTerminalBonus> list = util.importExcel(file.getInputStream(), 1);
432
-        
499
+
433 500
         // 过滤null元素,避免空指针异常
434 501
         if (list != null) {
435 502
             list.removeIf(item -> item == null);
@@ -437,7 +504,9 @@ public class LedgerImportController extends BaseController {
437 504
         if (list == null || list.isEmpty()) {
438 505
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
439 506
         }
440
-        
507
+        // 批量预加载组织信息
508
+        orgInfoHelper.batchFillOrgInfo(list, LedgerTerminalBonus::getPersonName);
509
+
441 510
         list.forEach(item -> {
442 511
             item.setImportBatch(batchNo);
443 512
             item.setSourceType("1");
@@ -484,6 +553,9 @@ public class LedgerImportController extends BaseController {
484 553
         if (list == null || list.isEmpty()) {
485 554
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
486 555
         }
556
+        // 批量预加载组织信息
557
+        orgInfoHelper.batchFillOrgInfo(list, LedgerExamScore::getPersonName);
558
+
487 559
         list.forEach(item -> {
488 560
             item.setImportBatch(batchNo);
489 561
             item.setSourceType("1");
@@ -507,6 +579,9 @@ public class LedgerImportController extends BaseController {
507 579
         if (list == null || list.isEmpty()) {
508 580
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
509 581
         }
582
+        // 批量预加载组织信息
583
+        orgInfoHelper.batchFillOrgInfo(list, LedgerRewardApproval::getPersonName);
584
+
510 585
         list.forEach(item -> {
511 586
             item.setImportBatch(batchNo);
512 587
             item.setSourceType("1");
@@ -553,7 +628,11 @@ public class LedgerImportController extends BaseController {
553 628
         if (list == null || list.isEmpty()) {
554 629
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
555 630
         }
556
-        list.forEach(item -> { item.setImportBatch(batchNo); item.setSourceType("1"); item.setCreateBy(getUsername()); });
631
+        list.forEach(item -> {
632
+            item.setImportBatch(batchNo);
633
+            item.setSourceType("1");
634
+            item.setCreateBy(getUsername());
635
+        });
557 636
         dailyTrainingService.batchInsert(list);
558 637
         return AjaxResult.success("导入成功,共" + list.size() + "条");
559 638
     }
@@ -572,7 +651,12 @@ public class LedgerImportController extends BaseController {
572 651
         if (list == null || list.isEmpty()) {
573 652
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
574 653
         }
575
-        list.forEach(item -> { item.setImportBatch(batchNo); item.setSourceType("1"); item.setCreateBy(getUsername()); });
654
+
655
+        list.forEach(item -> {
656
+            item.setImportBatch(batchNo);
657
+            item.setSourceType("1");
658
+            item.setCreateBy(getUsername());
659
+        });
576 660
         leaderDutyService.batchInsert(list);
577 661
         return AjaxResult.success("导入成功,共" + list.size() + "条");
578 662
     }
@@ -591,7 +675,11 @@ public class LedgerImportController extends BaseController {
591 675
         if (list == null || list.isEmpty()) {
592 676
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
593 677
         }
594
-        list.forEach(item -> { item.setImportBatch(batchNo); item.setSourceType("1"); item.setCreateBy(getUsername()); });
678
+        list.forEach(item -> {
679
+            item.setImportBatch(batchNo);
680
+            item.setSourceType("1");
681
+            item.setCreateBy(getUsername());
682
+        });
595 683
         healthSoldierService.batchInsert(list);
596 684
         return AjaxResult.success("导入成功,共" + list.size() + "条");
597 685
     }
@@ -610,7 +698,12 @@ public class LedgerImportController extends BaseController {
610 698
         if (list == null || list.isEmpty()) {
611 699
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
612 700
         }
613
-        list.forEach(item -> { item.setImportBatch(batchNo); item.setSourceType("1"); item.setCreateBy(getUsername()); });
701
+
702
+        list.forEach(item -> {
703
+            item.setImportBatch(batchNo);
704
+            item.setSourceType("1");
705
+            item.setCreateBy(getUsername());
706
+        });
614 707
         dormFireSafetyService.batchInsert(list);
615 708
         return AjaxResult.success("导入成功,共" + list.size() + "条");
616 709
     }
@@ -629,7 +722,12 @@ public class LedgerImportController extends BaseController {
629 722
         if (list == null || list.isEmpty()) {
630 723
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
631 724
         }
632
-        list.forEach(item -> { item.setImportBatch(batchNo); item.setSourceType("1"); item.setCreateBy(getUsername()); });
725
+
726
+        list.forEach(item -> {
727
+            item.setImportBatch(batchNo);
728
+            item.setSourceType("1");
729
+            item.setCreateBy(getUsername());
730
+        });
633 731
         trainingIssueService.batchInsert(list);
634 732
         return AjaxResult.success("导入成功,共" + list.size() + "条");
635 733
     }
@@ -652,7 +750,12 @@ public class LedgerImportController extends BaseController {
652 750
         if (list == null || list.isEmpty()) {
653 751
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
654 752
         }
655
-        list.forEach(item -> { item.setImportBatch(batchNo); item.setSourceType("1"); item.setCreateBy(getUsername()); });
753
+
754
+        list.forEach(item -> {
755
+            item.setImportBatch(batchNo);
756
+            item.setSourceType("1");
757
+            item.setCreateBy(getUsername());
758
+        });
656 759
         rewardPenaltyService.batchInsert(list);
657 760
         return AjaxResult.success("导入成功,共" + list.size() + "条");
658 761
     }
@@ -675,6 +778,9 @@ public class LedgerImportController extends BaseController {
675 778
         if (list == null || list.isEmpty()) {
676 779
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
677 780
         }
781
+        // 批量预加载组织信息
782
+        orgInfoHelper.batchFillOrgInfo(list, LedgerLeaveSpecial::getPersonName);
783
+
678 784
         list.forEach(item -> { item.setImportBatch(batchNo); item.setSourceType("1"); item.setCreateBy(getUsername()); });
679 785
         if (CollUtil.isNotEmpty(list)) {
680 786
             List<LedgerLeaveSpecial> existList = leaveSpecialService.selectList(new LedgerLeaveSpecial());
@@ -721,7 +827,12 @@ public class LedgerImportController extends BaseController {
721 827
         if (list == null || list.isEmpty()) {
722 828
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
723 829
         }
724
-        list.forEach(item -> { item.setImportBatch(batchNo); item.setSourceType("1"); item.setCreateBy(getUsername()); });
830
+
831
+        list.forEach(item -> {
832
+            item.setImportBatch(batchNo);
833
+            item.setSourceType("1");
834
+            item.setCreateBy(getUsername());
835
+        });
725 836
         bannerLetterService.batchInsert(list);
726 837
         return AjaxResult.success("导入成功,共" + list.size() + "条");
727 838
     }
@@ -744,6 +855,9 @@ public class LedgerImportController extends BaseController {
744 855
         if (list == null || list.isEmpty()) {
745 856
             return AjaxResult.error("导入数据为空或解析失败,请检查Excel表头是否与模板一致");
746 857
         }
858
+        // 批量预加载组织信息(性能优化)
859
+        orgInfoHelper.batchFillOrgInfo(list, LedgerQualificationLevel::getPersonName);
860
+
747 861
         list.forEach(item -> { item.setImportBatch(batchNo); item.setSourceType("1"); item.setCreateBy(getUsername()); });
748 862
         if (CollUtil.isNotEmpty(list)) {
749 863
             List<LedgerQualificationLevel> existList = qualificationLevelService.selectQueryList(new LedgerQualificationLevel());

+ 528 - 0
airport-ledger/src/main/java/com/sundot/airport/ledger/util/OrgInfoHelper.java

@@ -0,0 +1,528 @@
1
+package com.sundot.airport.ledger.util;
2
+
3
+import com.sundot.airport.common.core.domain.entity.SysDept;
4
+import com.sundot.airport.common.core.domain.entity.SysUser;
5
+import com.sundot.airport.common.enums.DeptType;
6
+import com.sundot.airport.system.domain.BasePosition;
7
+import com.sundot.airport.system.mapper.BasePositionMapper;
8
+import com.sundot.airport.system.mapper.SysDeptMapper;
9
+import com.sundot.airport.system.mapper.SysUserMapper;
10
+import org.springframework.beans.factory.annotation.Autowired;
11
+import org.springframework.stereotype.Component;
12
+
13
+import java.util.HashMap;
14
+import java.util.List;
15
+import java.util.Map;
16
+import java.util.Set;
17
+
18
+import java.util.stream.Collectors;
19
+
20
+/**
21
+ * 组织信息补充工具类
22
+ */
23
+@Component
24
+public class OrgInfoHelper {
25
+
26
+    @Autowired
27
+    private SysUserMapper sysUserMapper;
28
+
29
+    @Autowired
30
+    private SysDeptMapper sysDeptMapper;
31
+    
32
+    @Autowired
33
+    private BasePositionMapper basePositionMapper;
34
+    
35
+    // 缓存
36
+    private volatile Map<String, SysUser> localUserCache = null;
37
+    private volatile Map<Long, SysDept> localDeptCache = null;
38
+    private volatile Map<String, Map<String, Object>> localOrgInfoCache = new HashMap<>();
39
+    
40
+    // 位置信息缓存
41
+    private volatile Map<String, Long> localPositionCache = null;
42
+    private volatile Map<String, Long> localAreaCache = null;
43
+    private volatile Map<String, Long> localChannelCache = null;
44
+    
45
+    // 缓存过期时间(毫秒)
46
+    private volatile long cacheTimestamp = 0L;
47
+    private static final long CACHE_EXPIRE_MILLIS = 24 * 60 * 60 * 1000; // 24小时
48
+
49
+
50
+    
51
+    /**
52
+     * 【关键】确保基础缓存已加载到内存(双重检查锁 + 过期检查)
53
+     */
54
+    private void ensureLocalCacheLoaded() {
55
+        long now = System.currentTimeMillis();
56
+        
57
+        // 检查缓存是否过期
58
+        boolean expired = (now - cacheTimestamp) > CACHE_EXPIRE_MILLIS;
59
+        
60
+        if (localUserCache == null || localDeptCache == null || localPositionCache == null || expired) {
61
+            synchronized (this) {
62
+                if (localUserCache == null || localDeptCache == null || localPositionCache == null || expired) {
63
+                    loadLocalCacheFromDB();
64
+                    cacheTimestamp = System.currentTimeMillis(); // 记录缓存时间
65
+                }
66
+            }
67
+        }
68
+    }
69
+    
70
+    /**
71
+     * 直接从数据库加载基础缓存到本地内存(纯内存,零Redis)
72
+     */
73
+    private void loadLocalCacheFromDB() {
74
+        // 1. 查询所有用户
75
+        Map<String, SysUser> userCache = new HashMap<>();
76
+        List<SysUser> allUsers = sysUserMapper.selectUserList(new SysUser());
77
+        if (allUsers != null) {
78
+            for (SysUser user : allUsers) {
79
+                if (user.getNickName() != null && !user.getNickName().trim().isEmpty()) {
80
+                    userCache.put(user.getNickName().trim(), user);
81
+                }
82
+            }
83
+        }
84
+        localUserCache = userCache;
85
+        
86
+        // 2. 查询所有部门
87
+        Map<Long, SysDept> deptCache = new HashMap<>();
88
+        List<SysDept> allDepts = sysDeptMapper.selectDeptList(new SysDept());
89
+        if (allDepts != null) {
90
+            for (SysDept dept : allDepts) {
91
+                deptCache.put(dept.getDeptId(), dept);
92
+            }
93
+        }
94
+        localDeptCache = deptCache;
95
+        
96
+        // 3. 查询所有位置并缓存
97
+        List<BasePosition> allPositions = basePositionMapper.selectBasePositionList(new BasePosition());
98
+        Map<String, Long> positionCache = new HashMap<>();
99
+        Map<String, Long> areaCache = new HashMap<>();
100
+        Map<String, Long> channelCache = new HashMap<>();
101
+        
102
+        if (allPositions != null) {
103
+            for (BasePosition pos : allPositions) {
104
+                if (pos.getName() != null && !pos.getName().trim().isEmpty()) {
105
+                    String name = pos.getName().trim();
106
+                    String type = pos.getPositionType();
107
+                    
108
+                    if ("POSITION".equals(type)) {
109
+                        positionCache.put(name, pos.getId());
110
+                    } else if ("REGIONAL".equals(type)) {
111
+                        areaCache.put(name, pos.getId());
112
+                    } else if ("CHANNEL".equals(type)) {
113
+                        channelCache.put(name, pos.getId());
114
+                    }
115
+                }
116
+            }
117
+        }
118
+        
119
+        localPositionCache = positionCache;
120
+        localAreaCache = areaCache;
121
+        localChannelCache = channelCache;
122
+    }
123
+    
124
+    /**
125
+     * 从内存中的基础缓存构建组织信息
126
+     */
127
+    private Map<String, Object> buildOrgInfoFromMemory(Map<String, SysUser> userCache, 
128
+                                                        Map<Long, SysDept> deptCache, 
129
+                                                        String personName) {
130
+        Map<String, Object> result = new HashMap<>();
131
+        if (personName == null || userCache == null || deptCache == null) {
132
+            return result;
133
+        }
134
+        
135
+        // 从缓存中获取用户
136
+        SysUser user = userCache.get(personName);
137
+        if (user == null || user.getDeptId() == null) {
138
+            return result;
139
+        }
140
+
141
+        result.put("userId", user.getUserId());
142
+
143
+        // 从缓存中获取部门
144
+        SysDept userDept = deptCache.get(user.getDeptId());
145
+        if (userDept == null) {
146
+            return result;
147
+        }
148
+
149
+        String deptType = userDept.getDeptType();
150
+
151
+        // 根据部门类型构建层级关系(全部从缓存获取父级部门)
152
+        if (DeptType.STATION.getCode().equals(deptType)) {
153
+            result.put("stationId", user.getDeptId());
154
+            result.put("stationName", userDept.getDeptName());
155
+        } else if (DeptType.BRIGADE.getCode().equals(deptType)) {
156
+            result.put("deptId", user.getDeptId());
157
+            result.put("deptName", userDept.getDeptName());
158
+            Long parentId = userDept.getParentId();
159
+            if (parentId != null && parentId != 0) {
160
+                SysDept parentDept = deptCache.get(parentId);
161
+                if (parentDept != null) {
162
+                    result.put("stationId", parentDept.getDeptId());
163
+                    result.put("stationName", parentDept.getDeptName());
164
+                }
165
+            }
166
+        } else if (DeptType.MANAGER.getCode().equals(deptType)) {
167
+            result.put("teamId", user.getDeptId());
168
+            result.put("teamName", userDept.getDeptName());
169
+            Long parentId = userDept.getParentId();
170
+            if (parentId != null && parentId != 0) {
171
+                SysDept parentDept = deptCache.get(parentId);
172
+                if (parentDept != null) {
173
+                    result.put("deptId", parentDept.getDeptId());
174
+                    result.put("deptName", parentDept.getDeptName());
175
+                    Long grandParentId = parentDept.getParentId();
176
+                    if (grandParentId != null && grandParentId != 0) {
177
+                        SysDept grandParentDept = deptCache.get(grandParentId);
178
+                        if (grandParentDept != null) {
179
+                            result.put("stationId", grandParentDept.getDeptId());
180
+                            result.put("stationName", grandParentDept.getDeptName());
181
+                        }
182
+                    }
183
+                }
184
+            }
185
+        } else if (DeptType.TEAMS.getCode().equals(deptType)) {
186
+            result.put("groupId", user.getDeptId());
187
+            result.put("groupName", userDept.getDeptName());
188
+            Long parentId = userDept.getParentId();
189
+            if (parentId != null && parentId != 0) {
190
+                SysDept parentDept = deptCache.get(parentId);
191
+                if (parentDept != null) {
192
+                    result.put("teamId", parentDept.getDeptId());
193
+                    result.put("teamName", parentDept.getDeptName());
194
+                    Long grandParentId = parentDept.getParentId();
195
+                    if (grandParentId != null && grandParentId != 0) {
196
+                        SysDept grandParentDept = deptCache.get(grandParentId);
197
+                        if (grandParentDept != null) {
198
+                            result.put("deptId", grandParentDept.getDeptId());
199
+                            result.put("deptName", grandParentDept.getDeptName());
200
+                            Long greatGrandParentId = grandParentDept.getParentId();
201
+                            if (greatGrandParentId != null && greatGrandParentId != 0) {
202
+                                SysDept greatGrandParentDept = deptCache.get(greatGrandParentId);
203
+                                if (greatGrandParentDept != null) {
204
+                                    result.put("stationId", greatGrandParentDept.getDeptId());
205
+                                    result.put("stationName", greatGrandParentDept.getDeptName());
206
+                                }
207
+                            }
208
+                        }
209
+                    }
210
+                }
211
+            }
212
+        }
213
+
214
+        return result;
215
+    }
216
+
217
+
218
+
219
+    /**
220
+     * 批量填充组织信息
221
+     * 【极致优化】全部使用内存缓存,零Redis查询
222
+     */
223
+    public <T> void batchFillOrgInfo(List<T> objects, java.util.function.Function<T, String> nameGetter) {
224
+        if (objects == null || objects.isEmpty()) {
225
+            return;
226
+        }
227
+        
228
+        // 1. 收集所有需要查询的姓名
229
+        List<String> names = objects.stream()
230
+            .map(nameGetter)
231
+            .filter(name -> name != null && !name.trim().isEmpty())
232
+            .distinct()
233
+            .map(String::trim)
234
+            .collect(Collectors.toList());
235
+        
236
+        // 2. 确保基础缓存已加载到内存
237
+        ensureLocalCacheLoaded();
238
+        
239
+        // 3. 【关键】从内存缓存中批量构建组织信息
240
+        Map<String, Map<String, Object>> orgInfoMap = new HashMap<>();
241
+        
242
+        for (String name : names) {
243
+            // 先从本地缓存查找
244
+            Map<String, Object> cached = localOrgInfoCache.get(name);
245
+            if (cached != null) {
246
+                orgInfoMap.put(name, cached);
247
+                continue;
248
+            }
249
+            
250
+            // 从基础缓存构建
251
+            Map<String, Object> orgInfo = buildOrgInfoFromMemory(localUserCache, localDeptCache, name);
252
+            if (!orgInfo.isEmpty()) {
253
+                orgInfoMap.put(name, orgInfo);
254
+                localOrgInfoCache.put(name, orgInfo); // 存入本地缓存
255
+            }
256
+        }
257
+        
258
+        // 4. 从内存快速填充(零Redis查询)
259
+        Map<String, Map<String, Object>> finalOrgInfoMap = orgInfoMap;
260
+        objects.forEach(obj -> {
261
+            String name = nameGetter.apply(obj);
262
+            if (name != null && !name.trim().isEmpty()) {
263
+                Map<String, Object> orgInfo = finalOrgInfoMap.get(name.trim());
264
+                if (orgInfo != null && !orgInfo.isEmpty()) {
265
+                    try {
266
+                        setFieldValue(obj, "userId", orgInfo.get("userId"));
267
+                        setFieldValue(obj, "personId", orgInfo.get("userId"));
268
+                        setFieldValue(obj, "personUserId", orgInfo.get("userId"));
269
+                        setFieldValue(obj, "deptId", orgInfo.get("deptId"));
270
+                        setFieldValue(obj, "deptName", orgInfo.get("deptName"));
271
+                        setFieldValue(obj, "teamId", orgInfo.get("teamId"));
272
+                        setFieldValue(obj, "teamName", orgInfo.get("teamName"));
273
+                        setFieldValue(obj, "groupId", orgInfo.get("groupId"));
274
+                        setFieldValue(obj, "groupName", orgInfo.get("groupName"));
275
+                    } catch (Exception e) {
276
+                        // 忽略异常
277
+                    }
278
+                }
279
+            }
280
+        });
281
+    }
282
+
283
+    /**
284
+     * 批量填充组织信息和位置信息(同时处理人员组织和位置)
285
+     */
286
+    public <T> void batchFillOrgAndLocationInfo(
287
+            List<T> objects,
288
+            java.util.function.Function<T, String> nameGetter,
289
+            java.util.function.Function<T, String> positionGetter,
290
+            java.util.function.Function<T, String> locationGetter,
291
+            java.util.function.Function<T, String> channelGetter) {
292
+        if (objects == null || objects.isEmpty()) {
293
+            return;
294
+        }
295
+        
296
+        // 1. 收集所有需要查询的姓名
297
+        List<String> names = objects.stream()
298
+            .map(nameGetter)
299
+            .filter(name -> name != null && !name.trim().isEmpty())
300
+            .distinct()
301
+            .map(String::trim)
302
+            .collect(Collectors.toList());
303
+        
304
+        // 2. 确保基础缓存已加载到内存(包含位置信息)
305
+        ensureLocalCacheLoaded();
306
+        
307
+        // 3. 从内存缓存中批量构建组织信息
308
+        Map<String, Map<String, Object>> orgInfoMap = new HashMap<>();
309
+        for (String name : names) {
310
+            Map<String, Object> cached = localOrgInfoCache.get(name);
311
+            if (cached != null) {
312
+                orgInfoMap.put(name, cached);
313
+                continue;
314
+            }
315
+            
316
+            Map<String, Object> orgInfo = buildOrgInfoFromMemory(localUserCache, localDeptCache, name);
317
+            if (!orgInfo.isEmpty()) {
318
+                orgInfoMap.put(name, orgInfo);
319
+                localOrgInfoCache.put(name, orgInfo);
320
+            }
321
+        }
322
+        Map<String, Map<String, Object>> finalOrgInfoMap = orgInfoMap;
323
+        Map<String, Long> finalPositionCache = localPositionCache;
324
+        Map<String, Long> finalAreaCache = localAreaCache;
325
+        Map<String, Long> finalChannelCache = localChannelCache;
326
+        
327
+        objects.forEach(obj -> {
328
+            try {
329
+                // 填充组织信息
330
+                String name = nameGetter.apply(obj);
331
+                if (name != null && !name.trim().isEmpty()) {
332
+                    Map<String, Object> orgInfo = finalOrgInfoMap.get(name.trim());
333
+                    if (orgInfo != null && !orgInfo.isEmpty()) {
334
+                        setFieldValue(obj, "userId", orgInfo.get("userId"));
335
+                        setFieldValue(obj, "personId", orgInfo.get("userId"));
336
+                        setFieldValue(obj, "personUserId", orgInfo.get("userId"));
337
+                        setFieldValue(obj, "deptId", orgInfo.get("deptId"));
338
+                        setFieldValue(obj, "deptName", orgInfo.get("deptName"));
339
+                        setFieldValue(obj, "teamId", orgInfo.get("teamId"));
340
+                        setFieldValue(obj, "teamName", orgInfo.get("teamName"));
341
+                        setFieldValue(obj, "groupId", orgInfo.get("groupId"));
342
+                        setFieldValue(obj, "groupName", orgInfo.get("groupName"));
343
+                    }
344
+                }
345
+                
346
+                // 填充位置信息
347
+                if (positionGetter != null) {
348
+                    String positionName = positionGetter.apply(obj);
349
+                    if (positionName != null && !positionName.trim().isEmpty()) {
350
+                        Long positionId = finalPositionCache.get(positionName.trim());
351
+                        if (positionId != null) {
352
+                            setFieldValue(obj, "positionId", positionId);
353
+                        }
354
+                    }
355
+                }
356
+                
357
+                if (locationGetter != null) {
358
+                    String locationName = locationGetter.apply(obj);
359
+                    if (locationName != null && !locationName.trim().isEmpty()) {
360
+                        Long areaId = finalAreaCache.get(locationName.trim());
361
+                        if (areaId != null) {
362
+                            setFieldValue(obj, "areaId", areaId);
363
+                        }
364
+                    }
365
+                }
366
+                
367
+                if (channelGetter != null) {
368
+                    String channelName = channelGetter.apply(obj);
369
+                    if (channelName != null && !channelName.trim().isEmpty()) {
370
+                        Long channelId = finalChannelCache.get(channelName.trim());
371
+                        if (channelId != null) {
372
+                            setFieldValue(obj, "channelId", channelId);
373
+                        }
374
+                    }
375
+                }
376
+            } catch (Exception e) {
377
+                // 忽略异常
378
+            }
379
+        });
380
+    }
381
+
382
+    /**
383
+     * 仅根据组织名称填充组织ID(不通过人员姓名)
384
+     * 适用于有组织名称但没有人员姓名的台账
385
+     */
386
+    public <T> void batchFillOrgIdByNameOnly(
387
+            List<T> objects,
388
+            java.util.function.Function<T, String> deptNameGetter,
389
+            java.util.function.Function<T, String> teamNameGetter,
390
+            java.util.function.Function<T, String> groupNameGetter) {
391
+        if (objects == null || objects.isEmpty()) {
392
+            return;
393
+        }
394
+        
395
+        // 确保部门缓存已加载到内存
396
+        ensureLocalCacheLoaded();
397
+        
398
+        // 从内存获取部门缓存
399
+        Map<Long, SysDept> deptCache = localDeptCache;
400
+        if (deptCache == null || deptCache.isEmpty()) {
401
+            return;
402
+        }
403
+        
404
+        // 建立部门名称→ID的映射
405
+        Map<String, Long> deptNameMap = new HashMap<>();
406
+        for (SysDept dept : deptCache.values()) {
407
+            if (dept.getDeptName() != null && !dept.getDeptName().trim().isEmpty()) {
408
+                deptNameMap.put(dept.getDeptName().trim(), dept.getDeptId());
409
+            }
410
+        }
411
+        
412
+        // 填充组织ID
413
+        objects.forEach(obj -> {
414
+            try {
415
+                // 填充部门ID
416
+                if (deptNameGetter != null) {
417
+                    String deptName = deptNameGetter.apply(obj);
418
+                    if (deptName != null && !deptName.trim().isEmpty()) {
419
+                        Long deptId = deptNameMap.get(deptName.trim());
420
+                        if (deptId != null) {
421
+                            setFieldValueSilently(obj, "deptId", deptId);
422
+                        }
423
+                    }
424
+                }
425
+                
426
+                // 填充队室ID
427
+                if (teamNameGetter != null) {
428
+                    String teamName = teamNameGetter.apply(obj);
429
+                    if (teamName != null && !teamName.trim().isEmpty()) {
430
+                        Long teamId = deptNameMap.get(teamName.trim());
431
+                        if (teamId != null) {
432
+                            setFieldValueSilently(obj, "teamId", teamId);
433
+                        }
434
+                    }
435
+                }
436
+                
437
+                // 填充小组ID
438
+                if (groupNameGetter != null) {
439
+                    String groupName = groupNameGetter.apply(obj);
440
+                    if (groupName != null && !groupName.trim().isEmpty()) {
441
+                        Long groupId = deptNameMap.get(groupName.trim());
442
+                        if (groupId != null) {
443
+                            setFieldValueSilently(obj, "groupId", groupId);
444
+                        }
445
+                    }
446
+                }
447
+            } catch (Exception ignored) {
448
+                // 忽略异常
449
+            }
450
+        });
451
+    }
452
+
453
+    /**
454
+     * 静默设置字段值(不抛异常)
455
+     */
456
+    private void setFieldValueSilently(Object obj, String fieldName, Object value) {
457
+        try {
458
+            if (value != null) {
459
+                java.lang.reflect.Field field = obj.getClass().getDeclaredField(fieldName);
460
+                field.setAccessible(true);
461
+                field.set(obj, value);
462
+            }
463
+        } catch (Exception ignored) {
464
+            // 忽略异常
465
+        }
466
+    }
467
+
468
+    /**
469
+     * 通过反射设置字段值
470
+     */
471
+    private void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
472
+        if (value == null) {
473
+            return;
474
+        }
475
+        Class<?> clazz = obj.getClass();
476
+        try {
477
+            java.lang.reflect.Field field = clazz.getDeclaredField(fieldName);
478
+            field.setAccessible(true);
479
+            field.set(obj, value);
480
+        } catch (NoSuchFieldException ignored) {
481
+            // 字段不存在,忽略
482
+        }
483
+    }
484
+    
485
+    /**
486
+     * 手动触发构建完整缓存(可用于预热)
487
+     */
488
+    public void buildCompleteOrgCache() {
489
+        ensureLocalCacheLoaded();
490
+    }
491
+    
492
+    /**
493
+     * 【新增】立即清理所有缓存,释放内存
494
+     */
495
+    public void clearCache() {
496
+        synchronized (this) {
497
+            localUserCache = null;
498
+            localDeptCache = null;
499
+            localOrgInfoCache.clear();
500
+            localPositionCache = null;
501
+            localAreaCache = null;
502
+            localChannelCache = null;
503
+            cacheTimestamp = 0L;
504
+        }
505
+    }
506
+    
507
+    /**
508
+     * 【新增】获取当前缓存状态信息
509
+     */
510
+    public String getCacheStatus() {
511
+        if (localUserCache == null || localDeptCache == null) {
512
+            return "缓存未加载";
513
+        }
514
+        long now = System.currentTimeMillis();
515
+        long age = (now - cacheTimestamp) / 1000; // 秒
516
+        long remain = (CACHE_EXPIRE_MILLIS - (now - cacheTimestamp)) / 1000; // 剩余秒数
517
+        return String.format("缓存已加载 | 用户: %d | 部门: %d | 组织信息: %d | 岗位: %d | 区域: %d | 通道: %d | 已存在: %d秒 | 剩余: %d秒",
518
+            localUserCache.size(), 
519
+            localDeptCache.size(), 
520
+            localOrgInfoCache.size(),
521
+            localPositionCache != null ? localPositionCache.size() : 0,
522
+            localAreaCache != null ? localAreaCache.size() : 0,
523
+            localChannelCache != null ? localChannelCache.size() : 0,
524
+            age,
525
+            Math.max(0, remain)
526
+        );
527
+    }
528
+}