Переглянути джерело

feat: 接口分页上限调整

BaiLuoYan 6 годин тому
батько
коміт
cf3dbc03da

+ 2 - 2
README.md

@@ -1402,9 +1402,9 @@ Content-Type: application/json
 | username | string | 否 | 用户名(模糊匹配) |
 | nickname | string | 否 | 昵称(模糊匹配) |
 | status | int64 | 否 | 状态筛选(1=启用,2=禁用) |
-| deptId | *int64 | 否 | 部门 ID(0 表示无部门) |
+| deptIds | []int64 | 否 | 部门 ID 列表(支持多值,传 [0] 表示查询无部门用户) |
 | page | int64 | 否 | 页码 |
-| pageSize | int64 | 否 | 每页条数 |
+| pageSize | int64 | 否 | 每页条数,上限 10000 |
 
 #### POST /api/user/detail — 用户详情
 

+ 2 - 2
internal/logic/user/userListLogic.go

@@ -58,13 +58,13 @@ func (l *UserListLogic) UserList(req *types.UserListReq) (resp *types.PageResp,
 		}
 		memberMap = mtMap
 	} else {
-		hasFilter := req.Username != "" || req.Nickname != "" || req.Status != 0 || req.DeptId != nil
+		hasFilter := req.Username != "" || req.Nickname != "" || req.Status != 0 || len(req.DeptIds) > 0
 		if hasFilter {
 			filter := userModel.UserListFilter{
 				Username: req.Username,
 				Nickname: req.Nickname,
 				Status:   req.Status,
-				DeptId:   req.DeptId,
+				DeptIds:  req.DeptIds,
 			}
 			list, total, err = l.svcCtx.SysUserModel.FindListByFilter(l.ctx, filter, page, pageSize)
 		} else {

+ 4 - 4
internal/logic/user/userListLogic_test.go

@@ -94,21 +94,21 @@ func TestUserList_WithoutProductCode(t *testing.T) {
 	t.Fatal("should find inserted user in the list")
 }
 
-// TC-0178: pageSize超过上限
-func TestUserList_PageSizeOver100_Capped(t *testing.T) {
+// TC-0178: pageSize超过上限(10000)时被截断
+func TestUserList_PageSizeOverLimit_Capped(t *testing.T) {
 	ctx := ctxhelper.SuperAdminCtx()
 	svcCtx := svc.NewServiceContext(testutil.GetTestConfig())
 
 	logic := NewUserListLogic(ctx, svcCtx)
 	resp, err := logic.UserList(&types.UserListReq{
 		Page:     1,
-		PageSize: 200,
+		PageSize: 20000,
 	})
 	require.NoError(t, err)
 	require.NotNil(t, resp)
 
 	items := resp.List.([]types.UserItem)
-	assert.LessOrEqual(t, len(items), 100)
+	assert.LessOrEqual(t, len(items), 10000)
 }
 
 // TC-0179: 用户不在产品中

+ 8 - 4
internal/model/user/sysUserModel.go

@@ -27,7 +27,7 @@ type (
 		Username string
 		Nickname string
 		Status   int64
-		DeptId   *int64
+		DeptIds  []int64
 	}
 
 	SysUserModel interface {
@@ -142,9 +142,13 @@ func (m *customSysUserModel) FindListByFilter(ctx context.Context, filter UserLi
 		conditions = append(conditions, "`status` = ?")
 		args = append(args, filter.Status)
 	}
-	if filter.DeptId != nil {
-		conditions = append(conditions, "`deptId` = ?")
-		args = append(args, *filter.DeptId)
+	if len(filter.DeptIds) > 0 {
+		placeholders := strings.Repeat("?,", len(filter.DeptIds))
+		placeholders = placeholders[:len(placeholders)-1]
+		conditions = append(conditions, "`deptId` IN ("+placeholders+")")
+		for _, id := range filter.DeptIds {
+			args = append(args, id)
+		}
 	}
 
 	where := ""

+ 7 - 7
internal/types/types.go

@@ -381,13 +381,13 @@ type UserItem struct {
 }
 
 type UserListReq struct {
-	ProductCode string `json:"productCode,optional"`
-	Username    string `json:"username,optional"`
-	Nickname    string `json:"nickname,optional"`
-	Status      int64  `json:"status,optional"`
-	DeptId      *int64 `json:"deptId,optional"`
-	Page        int64  `json:"page,optional"`
-	PageSize    int64  `json:"pageSize,optional"`
+	ProductCode string  `json:"productCode,optional"`
+	Username    string  `json:"username,optional"`
+	Nickname    string  `json:"nickname,optional"`
+	Status      int64   `json:"status,optional"`
+	DeptIds     []int64 `json:"deptIds,optional"`
+	Page        int64   `json:"page,optional"`
+	PageSize    int64   `json:"pageSize,optional"`
 }
 
 type UserPermItem struct {

+ 2 - 2
internal/util/validate.go

@@ -59,8 +59,8 @@ func NormalizePage(page, pageSize int64) (int64, int64) {
 	if pageSize <= 0 {
 		pageSize = 20
 	}
-	if pageSize > 100 {
-		pageSize = 100
+	if pageSize > 10000 {
+		pageSize = 10000
 	}
 	return page, pageSize
 }

+ 8 - 5
internal/util/validate_test.go

@@ -9,18 +9,21 @@ import (
 // TC-0269: page=2, pageSize=10
 func TestNormalizePage(t *testing.T) {
 	tests := []struct {
-		name             string
-		page, pageSize   int64
-		wantP, wantSize  int64
+		name            string
+		page, pageSize  int64
+		wantP, wantSize int64
 	}{
 		{"normal values", 2, 10, 2, 10},
 		{"page=0", 0, 10, 1, 10},
 		{"page=-1", -1, 10, 1, 10},
 		{"pageSize=0", 1, 0, 1, 20},
 		{"pageSize=-5", 1, -5, 1, 20},
-		{"pageSize>100", 1, 500, 1, 100},
+		{"pageSize=500", 1, 500, 1, 500},
 		{"pageSize=100 boundary", 1, 100, 1, 100},
-		{"pageSize=101 boundary", 1, 101, 1, 100},
+		{"pageSize=101 boundary", 1, 101, 1, 101},
+		{"pageSize=5000 boundary", 1, 5000, 1, 5000},
+		{"pageSize=10000 boundary", 1, 10000, 1, 10000},
+		{"pageSize=10001 over limit", 1, 10001, 1, 10000},
 		{"both zero", 0, 0, 1, 20},
 		{"both negative", -3, -10, 1, 20},
 	}

+ 7 - 7
perm.api

@@ -287,13 +287,13 @@ type (
 		Status   int64   `json:"status,optional"`
 	}
 	UserListReq {
-		ProductCode string `json:"productCode,optional"`
-		Username    string `json:"username,optional"`
-		Nickname    string `json:"nickname,optional"`
-		Status      int64  `json:"status,optional"`
-		DeptId      *int64 `json:"deptId,optional"`
-		Page        int64  `json:"page,optional"`
-		PageSize    int64  `json:"pageSize,optional"`
+		ProductCode string  `json:"productCode,optional"`
+		Username    string  `json:"username,optional"`
+		Nickname    string  `json:"nickname,optional"`
+		Status      int64   `json:"status,optional"`
+		DeptIds     []int64 `json:"deptIds,optional"`
+		Page        int64   `json:"page,optional"`
+		PageSize    int64   `json:"pageSize,optional"`
 	}
 	UserDetailReq {
 		Id          int64  `json:"id"`

+ 9 - 4
test-design.md

@@ -522,9 +522,13 @@ MySQL (InnoDB) + Redis Cache
 | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
 | TC-0176 | POST /api/user/list | 含productCode | `{"productCode":"p1","page":1,"pageSize":10}` | 每用户含memberType(批量查) | 正常路径 | P0 | FindMapByProductCodeUserIds |
 | TC-0177 | POST /api/user/list | 不含productCode | `{"page":1}` | memberType全空,不调批量查 | 分支覆盖 | P1 | productCode="" |
-| TC-0178 | POST /api/user/list | pageSize超过上限 | `{"pageSize":500}` | 实际pageSize=100 | 边界 | P0 | NormalizePage cap |
+| TC-0178 | POST /api/user/list | pageSize超过上限 | `{"pageSize":20000}` | 实际pageSize=10000 | 边界 | P0 | NormalizePage cap(上限已调整为 10000) |
 | TC-0179 | POST /api/user/list | 用户不在产品中 | productCode指定,部分用户不是成员 | memberType为空 | 分支覆盖 | P1 | memberMap无对应key |
 | TC-0180 | POST /api/user/list | 批量查询DB异常 | FindMapByProductCodeUserIds失败 | code=500 | 异常路径 | P1 | err→透传 |
+| TC-1291 | POST /api/user/list | deptIds 单值过滤 | `{"deptIds":[5]}` | 仅返回 deptId=5 的用户 | 正常路径 | P0 | FindListByFilter DeptIds IN 子句 |
+| TC-1292 | POST /api/user/list | deptIds 多值过滤 | `{"deptIds":[5,6,7]}` | 仅返回 deptId IN (5,6,7) 的用户 | 正常路径 | P0 | IN 子句多值 |
+| TC-1293 | POST /api/user/list | deptIds=[0] 查无部门用户 | `{"deptIds":[0]}` | 仅返回 deptId=0 的用户 | 正常路径 | P0 | deptId=0 表示未分配部门 |
+| TC-1294 | POST /api/user/list | deptIds 为空不触发过滤 | `{}` | 返回全量用户(走 FindListByPage) | 分支覆盖 | P1 | len(DeptIds)==0 → hasFilter=false |
 | TC-1267 | POST /api/user/detail | 超管不传 productCode → roleIds 含目标用户所有产品角色 | 超管 ctx + `{"id":userId}`(不传 productCode) | 含全量 roleIds(跨产品) | 正常路径 | P0 | userDetailLogic;超管无产品上下文时返回全量 |
 | TC-1268 | POST /api/user/detail | 超管传 productCode → roleIds 只含该产品角色 | 超管 ctx + `{"id":userId,"productCode":"test_product"}` | roleIds 仅含 test_product 下的角色 | 正常路径 | P0 | 超管显式指定产品时按产品过滤 |
 | TC-0182 | POST /api/user/detail | 正常查询-含Avatar | 有Avatar用户 | avatar字段非空 | 分支覆盖 | P1 | Avatar.Valid=true |
@@ -862,10 +866,11 @@ MySQL (InnoDB) + Redis Cache
 | TC-0270 | page<=0 | page=0, pageSize=10 | (1, 10) | 边界 | P0 | page<=0→1 |
 | TC-0271 | page=-1 | page=-1, pageSize=10 | (1, 10) | 边界 | P0 | page<=0→1 |
 | TC-0272 | pageSize<=0 | page=1, pageSize=0 | (1, 20) | 边界 | P0 | pageSize<=0→20 |
-| TC-0273 | pageSize>100 | page=1, pageSize=500 | (1, 100) | 边界-上限 | P0 | pageSize>100→100 |
-| TC-0274 | pageSize=100 | page=1, pageSize=100 | (1, 100) | 边界 | P1 | 恰好不触发 |
-| TC-0275 | pageSize=101 | page=1, pageSize=101 | (1, 100) | 边界 | P1 | 恰好触发 |
+| TC-0273 | pageSize=500 | page=1, pageSize=500 | (1, 500) | 正常路径 | P1 | 500 < 10000,不截断 |
+| TC-0274 | pageSize=5000 | page=1, pageSize=5000 | (1, 5000) | 边界 | P1 | 恰好不触发上限 |
+| TC-0275 | pageSize=10000 | page=1, pageSize=10000 | (1, 10000) | 边界-上限 | P0 | 恰好等于上限,不截断 |
 | TC-0276 | 双零 | page=0, pageSize=0 | (1, 20) | 边界 | P1 | 两条件同时 |
+| TC-0290 | pageSize>10000 | page=1, pageSize=10001 | (1, 10000) | 边界-超限 | P0 | pageSize>10000→10000 |
 
 ### 5.2 IsValidEmail
 

+ 9 - 4
test-report.md

@@ -547,9 +547,13 @@
 | :--- | :--- | :--- |
 | TC-0176 | 含productCode | ✅ pass |
 | TC-0177 | 不含productCode | ✅ pass |
-| TC-0178 | pageSize超过上限 | ✅ pass |
+| TC-0178 | pageSize超过上限(10000)时被截断 | ✅ pass |
 | TC-0179 | 用户不在产品中 | ✅ pass |
 | TC-0180 | 批量查询DB异常 | ✅ pass |
+| TC-1291 | deptIds 单值过滤 | ✅ pass |
+| TC-1292 | deptIds 多值过滤 | ✅ pass |
+| TC-1293 | deptIds=[0] 查无部门用户 | ✅ pass |
+| TC-1294 | deptIds 为空不触发过滤 | ✅ pass |
 | TC-1267 | userDetail 超管不传 productCode → roleIds 含全量角色(跨产品) | ✅ pass |
 | TC-1268 | userDetail 超管传 productCode → roleIds 只含该产品角色 | ✅ pass |
 | TC-0182 | 正常查询-含Avatar | ✅ pass |
@@ -892,10 +896,11 @@
 | TC-0270 | page<=0 | ✅ pass |
 | TC-0271 | page=-1 | ✅ pass |
 | TC-0272 | pageSize<=0 | ✅ pass |
-| TC-0273 | pageSize>100 | ✅ pass |
-| TC-0274 | pageSize=100 | ✅ pass |
-| TC-0275 | pageSize=101 | ✅ pass |
+| TC-0273 | pageSize=500(不截断) | ✅ pass |
+| TC-0274 | pageSize=5000(边界) | ✅ pass |
+| TC-0275 | pageSize=10000(上限边界) | ✅ pass |
 | TC-0276 | 双零 | ✅ pass |
+| TC-0290 | pageSize=10001(超限→10000) | ✅ pass |
 
 ### 5.2 IsValidEmail