فهرست منبع

fix(admin): 会员中心等级列表/统计/升级记录N+1三处修复

- MembershipCenter.vue: loadLevels/loadOverview 的 request 拦截器返回结构判定错误
  (listMembershipLevels/getMembershipStats 走拦截器返回已解包 {code,data},
   误按裸 axios 判定 res.data.code) 导致等级配置列表永远为空、统计卡不更新
- StatsController: freeCount 查询 .or() 位置错误导致恒为 0,改为嵌套 and(w->eq.or.isNull)
- upgrade-records 后端批量填充 userName,消除前端逐条调 user/search 的 N+1 请求
- 删除测试中引用不存在方法 getAdvicesForLowScores 的死桩,恢复 testCompile
Xiaogang Liao 2 هفته پیش
والد
کامیت
7c8d89c3eb

+ 25 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java

@@ -1218,6 +1218,31 @@ public class AdminController {
         Page<MemberUpgradeRecord> pageParam = new Page<>(page, size);
         Page<MemberUpgradeRecord> pageParam = new Page<>(page, size);
         SortUtil.applySort(wrapper);
         SortUtil.applySort(wrapper);
         Page<MemberUpgradeRecord> result = memberUpgradeRecordMapper.selectPage(pageParam, wrapper);
         Page<MemberUpgradeRecord> result = memberUpgradeRecordMapper.selectPage(pageParam, wrapper);
+
+        // 批量填充 userName(避免前端逐条调 /api/admin/user/search 造成 N+1)
+        List<MemberUpgradeRecord> records = result.getRecords();
+        if (records != null && !records.isEmpty()) {
+            List<Long> userIds = new ArrayList<>();
+            for (MemberUpgradeRecord r : records) {
+                if (r.getUserId() != null && !userIds.contains(r.getUserId())) {
+                    userIds.add(r.getUserId());
+                }
+            }
+            if (!userIds.isEmpty()) {
+                List<User> users = userMapper.selectBatchIds(userIds);
+                Map<Long, String> nameMap = new HashMap<>();
+                for (User u : users) {
+                    if (u != null && u.getId() != null) {
+                        String display = u.getNickname() != null ? u.getNickname()
+                                : (u.getRealName() != null ? u.getRealName() : ("用户" + u.getId()));
+                        nameMap.put(u.getId(), display);
+                    }
+                }
+                for (MemberUpgradeRecord r : records) {
+                    r.setUserName(nameMap.getOrDefault(r.getUserId(), "用户" + r.getUserId()));
+                }
+            }
+        }
         return Result.success(result);
         return Result.success(result);
     }
     }
 
 

+ 2 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/stats/StatsController.java

@@ -206,7 +206,8 @@ public class StatsController {
         Map<String, Object> data = new HashMap<>();
         Map<String, Object> data = new HashMap<>();
         try {
         try {
             CompletableFuture<Long> freeFuture = CompletableFuture.supplyAsync(() ->
             CompletableFuture<Long> freeFuture = CompletableFuture.supplyAsync(() ->
-                userMapper.selectCount(new LambdaQueryWrapper<User>().or().eq(User::getMemberLevel, "FREE").isNull(User::getMemberLevel)), taskExecutor);
+                userMapper.selectCount(new LambdaQueryWrapper<User>()
+                    .and(w -> w.eq(User::getMemberLevel, "FREE").or().isNull(User::getMemberLevel))), taskExecutor);
             CompletableFuture<Long> familyFuture = CompletableFuture.supplyAsync(() ->
             CompletableFuture<Long> familyFuture = CompletableFuture.supplyAsync(() ->
                 userMapper.selectCount(new LambdaQueryWrapper<User>().eq(User::getMemberLevel, "FAMILY")), taskExecutor);
                 userMapper.selectCount(new LambdaQueryWrapper<User>().eq(User::getMemberLevel, "FAMILY")), taskExecutor);
             CompletableFuture<Long> providerFuture = CompletableFuture.supplyAsync(() ->
             CompletableFuture<Long> providerFuture = CompletableFuture.supplyAsync(() ->

+ 5 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/MemberUpgradeRecord.java

@@ -1,6 +1,7 @@
 package com.etotem.cfc.entity;
 package com.etotem.cfc.entity;
 
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.Data;
 import lombok.Data;
@@ -38,4 +39,8 @@ public class MemberUpgradeRecord implements Serializable {
     private Date approveTime;  // 审批时间
     private Date approveTime;  // 审批时间
 
 
     private Date createdAt;
     private Date createdAt;
+
+    /** 非表字段:冗余的用户昵称(列表展示用,由 Controller 批量填充) */
+    @TableField(exist = false)
+    private String userName;
 }
 }

+ 0 - 1
cfc-backend/src/test/java/com/etotem/cfc/integration/service/FiveDimensionSelfCheckServiceTest.java

@@ -53,7 +53,6 @@ public class FiveDimensionSelfCheckServiceTest {
         setField(service, "familyMemberMapper", familyMemberMapper);
         setField(service, "familyMemberMapper", familyMemberMapper);
 
 
         // 默认 mock:全部维度健康,无寻源建议
         // 默认 mock:全部维度健康,无寻源建议
-        when(wuxingSourcingService.getAdvicesForLowScores(anyMap())).thenReturn(new ArrayList<>());
         when(wuxingSourcingService.levelOf(anyInt())).thenReturn("healthy");
         when(wuxingSourcingService.levelOf(anyInt())).thenReturn("healthy");
         when(wuxingSourcingService.levelName(anyString())).thenReturn("健康");
         when(wuxingSourcingService.levelName(anyString())).thenReturn("健康");
     }
     }

+ 13 - 36
cfc-web/src/views/admin/MembershipCenter.vue

@@ -353,12 +353,14 @@ export default {
           getMembershipStats(),
           getMembershipStats(),
           this._request('/api/admin/membership/overview')
           this._request('/api/admin/membership/overview')
         ])
         ])
-        if (statsRes.data && statsRes.data.code === 200) {
-          const s = statsRes.data.data
+        // getMembershipStats 走 request 拦截器,返回 {code, message, data}
+        if (statsRes.code === 200 && statsRes.data) {
+          const s = statsRes.data
           this.statCards[0].value = s.freeCount || 0
           this.statCards[0].value = s.freeCount || 0
           this.statCards[1].value = s.familyCount || 0
           this.statCards[1].value = s.familyCount || 0
           this.statCards[2].value = s.providerCount || 0
           this.statCards[2].value = s.providerCount || 0
         }
         }
+        // _request 是裸 axios,需通过 res.data.code 解包
         if (overviewRes.data && overviewRes.data.code === 200) {
         if (overviewRes.data && overviewRes.data.code === 200) {
           const o = overviewRes.data.data || {}
           const o = overviewRes.data.data || {}
           this.statCards[3].value = o.newFamilyThisMonth || 0
           this.statCards[3].value = o.newFamilyThisMonth || 0
@@ -373,9 +375,10 @@ export default {
     async loadLevels() {
     async loadLevels() {
       this.levelsLoading = true
       this.levelsLoading = true
       try {
       try {
+        // listMembershipLevels 走 request 拦截器,返回已解包的 {code, message, data},data 即等级数组
         const res = await listMembershipLevels()
         const res = await listMembershipLevels()
-        if (res.data && res.data.code === 200) {
-          const list = res.data.data || []
+        if (res.code === 200) {
+          const list = res.data || []
           this.levels = Array.isArray(list) ? list.filter(function(l) { return l && typeof l === 'object' }) : []
           this.levels = Array.isArray(list) ? list.filter(function(l) { return l && typeof l === 'object' }) : []
         }
         }
       } catch (e) {
       } catch (e) {
@@ -397,7 +400,6 @@ export default {
           const records = (res.data.data && res.data.data.records) || []
           const records = (res.data.data && res.data.data.records) || []
           this.upgradeRecords = Array.isArray(records) ? records.filter(function(r) { return r && typeof r === 'object' }) : []
           this.upgradeRecords = Array.isArray(records) ? records.filter(function(r) { return r && typeof r === 'object' }) : []
           this.recordsTotal = res.data.data.total || 0
           this.recordsTotal = res.data.data.total || 0
-          await this.resolveUserNames(this.upgradeRecords, 'userId')
         }
         }
       } catch (e) {
       } catch (e) {
         this.$message.error('加载升级记录失败')
         this.$message.error('加载升级记录失败')
@@ -448,12 +450,12 @@ export default {
       this.editSubmitting = true
       this.editSubmitting = true
       try {
       try {
         const res = await updateMembershipLevel(this.editForm)
         const res = await updateMembershipLevel(this.editForm)
-        if (res.data && res.data.code === 200) {
-          this.$message && this.$message.success && this.$message.success(res.data.data || '更新成功')
+        if (res.code === 200) {
+          this.$message && this.$message.success && this.$message.success(res.data || '更新成功')
           this.editVisible = false
           this.editVisible = false
           this.loadLevels()
           this.loadLevels()
         } else {
         } else {
-          this.$message && this.$message.error && this.$message.error((res.data && res.data.message) || '更新失败')
+          this.$message && this.$message.error && this.$message.error(res.message || '更新失败')
         }
         }
       } catch (e) {
       } catch (e) {
         this.$message && this.$message.error && this.$message.error('更新失败')
         this.$message && this.$message.error && this.$message.error('更新失败')
@@ -462,31 +464,6 @@ export default {
       }
       }
     },
     },
 
 
-    resolveUserNames(items, idField) {
-      if (!items || items.length === 0) return Promise.resolve()
-      var ids = []
-      items.forEach(function(item) {
-        if (item[idField] && ids.indexOf(item[idField]) === -1) ids.push(item[idField])
-      })
-      var nameMap = {}
-      return Promise.all(ids.map(function(id) {
-        return searchUsers({ keyword: String(id) })
-          .then(function(res) {
-            if (res.data && res.data.length > 0) {
-              var u = res.data[0]
-              nameMap[id] = u.nickname || u.realName || ('用户' + id)
-            } else {
-              nameMap[id] = ('用户' + id)
-            }
-          })
-          .catch(function() { nameMap[id] = ('用户' + id) })
-      })).then(function() {
-        items.forEach(function(item) {
-          item.userName = nameMap[item[idField]] || ('用户' + item[idField])
-        })
-      })
-    },
-
     async handleMaintainSearch() {
     async handleMaintainSearch() {
       const kw = (this.maintainSearch || '').trim()
       const kw = (this.maintainSearch || '').trim()
       if (!kw) {
       if (!kw) {
@@ -532,13 +509,13 @@ export default {
         if (refStr === '0') payload.referrerId = ''
         if (refStr === '0') payload.referrerId = ''
         else if (refStr !== '') payload.referrerId = refStr
         else if (refStr !== '') payload.referrerId = refStr
         const res = await updateMembershipUser(payload)
         const res = await updateMembershipUser(payload)
-        if (res.data && res.data.code === 200) {
-          this.$message && this.$message.success && this.$message.success(res.data.data || '更新成功')
+        if (res.code === 200) {
+          this.$message && this.$message.success && this.$message.success(res.data || '更新成功')
           this.maintainVisible = false
           this.maintainVisible = false
           this.handleMaintainSearch()
           this.handleMaintainSearch()
           this.refreshAll()
           this.refreshAll()
         } else {
         } else {
-          this.$message && this.$message.error && this.$message.error(res.data && res.data.message || '更新失败')
+          this.$message && this.$message.error && this.$message.error(res.message || '更新失败')
         }
         }
       } catch (e) {
       } catch (e) {
         this.$message && this.$message.error && this.$message.error('更新失败')
         this.$message && this.$message.error && this.$message.error('更新失败')