Explorar o código

统计列表与导出新增昵称/账号模糊搜索

lh hai 1 mes
pai
achega
3f644ffcf3

+ 8 - 0
app/Services/PointsStatsService.php

@@ -132,6 +132,14 @@ class PointsStatsService
             }
         }
 
+        $nickname = trim((string)getProp($params, 'nickname', ''));
+        if ($nickname !== '') {
+            $query->where(function ($q) use ($nickname) {
+                $q->where('u.nickname', 'like', '%' . $nickname . '%')
+                    ->orWhere('u.account', 'like', '%' . $nickname . '%');
+            });
+        }
+
         $model = (string)getProp($params, 'model', '');
         if ($model !== '') {
             $query->where('s.model', $model);

+ 33 - 0
docs/superpowers/specs/2026-08-20-points-stats-nickname-search-design.md

@@ -0,0 +1,33 @@
+# 统计模块 nickname 模糊搜索设计
+
+日期:2026-08-20
+
+## 目标
+
+积分统计列表(`GET /api/points/stats`)与 CSV 导出(`GET /api/points/stats/export`)新增 `nickname` 模糊搜索:按用户昵称或账号模糊匹配。
+
+## 背景
+
+`PointsStatsService::buildStatsQuery` 是列表与导出共用的查询构建器,已 `leftJoin` 用户表 `mp_manage_users as u`。经确认当前生产库不存在 nickname 为空的用户;为保持与 `getFilters` 展示口径一致(空昵称时用 account 兜底),匹配字段同时覆盖 `nickname` 与 `account`。
+
+## 行为
+
+- 新增筛选参数 `nickname`(字符串),非空时生效:
+  `WHERE (u.nickname LIKE '%xxx%' OR u.account LIKE '%xxx%')`
+- 与现有 uid/model/relay/model_type/日期筛选叠加,权限(cpid)过滤不受影响。
+- 列表与导出共用 `buildStatsQuery`,两处自动同时支持,无需改控制器与路由。
+- 不做:前端筛选框(由前端自行提交参数)、`getFilters` 候选选项(模糊搜索为自由输入)。
+
+## 实现
+
+仅修改 `app/Services/PointsStatsService.php` 的 `buildStatsQuery`,在现有筛选分支后追加 nickname 分支。
+
+## 测试
+
+新增 `tests/Unit/PointsStatsServiceNicknameSearchTest.php`:
+- 带 `nickname` 参数时,生成的 SQL 包含 nickname/account 的 LIKE 条件,bindings 含 `%xxx%`;
+- 不带参数时无 LIKE 条件。
+
+## 验收
+
+`php vendor/bin/phpunit tests/Unit/PointsStatsServiceNicknameSearchTest.php` 通过;列表与导出请求带 `nickname` 参数返回对应用户的数据。

+ 45 - 0
tests/Unit/PointsStatsServiceNicknameSearchTest.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace Tests\Unit;
+
+use App\Services\PointsStatsService;
+use Tests\TestCase;
+
+class PointsStatsServiceNicknameSearchTest extends TestCase
+{
+    /**
+     * 反射调用 buildStatsQuery,返回查询构建器。
+     */
+    private function buildQuery(array $params, array $groupBy = ['uid'])
+    {
+        app()->instance('siteData', ['role' => 'superadmin', 'cpid' => 0]);
+
+        $service = new PointsStatsService();
+        $method = new \ReflectionMethod($service, 'buildStatsQuery');
+        $method->setAccessible(true);
+
+        [$query] = $method->invoke($service, $params, $groupBy);
+        return $query;
+    }
+
+    public function testNicknameParamAddsNicknameAndAccountLikeConditions()
+    {
+        $query = $this->buildQuery(['nickname' => 'Fenzu5']);
+
+        $sql = $query->toSql();
+
+        $this->assertStringContainsString('`u`.`nickname` like ?', $sql);
+        $this->assertStringContainsString('`u`.`account` like ?', $sql);
+        $this->assertContains('%Fenzu5%', $query->getBindings());
+    }
+
+    public function testEmptyNicknameParamDoesNotAddLikeConditions()
+    {
+        $query = $this->buildQuery(['nickname' => '']);
+
+        $sql = $query->toSql();
+
+        $this->assertStringNotContainsString('`u`.`nickname` like ?', $sql);
+        $this->assertStringNotContainsString('`u`.`account` like ?', $sql);
+    }
+}