feat: 完成用户列表查询接口
- 实现GET /api/user/list接口 - 支持分页查询、关键词搜索、状态筛选、排序等功能 - 创建createQuerySchema工具函数,合并分页和自定义参数 - 修复Schema类型问题,确保参数验证完整性 - 使用count(id)计算总数,确保返回数字类型 关联任务10.0
This commit is contained in:
parent
8bf3f6705a
commit
926564b144
@ -9,7 +9,8 @@
|
||||
|
||||
import { Elysia } from 'elysia';
|
||||
import { userService } from './user.service';
|
||||
import { GetCurrentUserResponsesSchema } from './user.response';
|
||||
import { GetCurrentUserResponsesSchema, GetUserListResponsesSchema } from './user.response';
|
||||
import { UserListQuerySchema } from './user.schema';
|
||||
import { tags } from '@/modules/tags';
|
||||
import { jwtAuthPlugin } from '@/plugins/jwt/jwt.plugins';
|
||||
import type { JwtUserType } from '@/type/jwt.type';
|
||||
@ -38,4 +39,24 @@ export const userController = new Elysia()
|
||||
},
|
||||
response: GetCurrentUserResponsesSchema,
|
||||
}
|
||||
)
|
||||
/**
|
||||
* 用户列表查询接口
|
||||
* @route GET /api/users
|
||||
* @description 获取用户列表,支持分页、搜索、筛选等功能,需要JWT认证
|
||||
*/
|
||||
.get(
|
||||
'/list',
|
||||
({ query }) => userService.getUserList(query),
|
||||
{
|
||||
query: UserListQuerySchema,
|
||||
detail: {
|
||||
summary: '获取用户列表',
|
||||
description: '获取用户列表,支持分页查询、关键词搜索、状态筛选、排序等功能',
|
||||
tags: [tags.user],
|
||||
operationId: 'getUserList',
|
||||
security: [{ bearerAuth: [] }]
|
||||
},
|
||||
response: GetUserListResponsesSchema,
|
||||
}
|
||||
);
|
@ -4,12 +4,12 @@
|
||||
* @date 2024-12-19
|
||||
* @lastEditor AI Assistant
|
||||
* @lastEditTime 2025-01-07
|
||||
* @description 定义用户模块的响应格式,包括获取当前用户信息等
|
||||
* @description 定义用户模块的响应格式,包括获取当前用户信息、用户列表查询等
|
||||
*/
|
||||
|
||||
import { t, type Static } from 'elysia';
|
||||
import { responseWrapperSchema } from '@/utils/responseFormate';
|
||||
import { CurrentUserSchema } from './user.schema';
|
||||
import { CurrentUserSchema, UserListResponseSchema } from './user.schema';
|
||||
|
||||
/**
|
||||
* 获取当前用户信息接口响应组合
|
||||
@ -37,5 +37,40 @@ export const GetCurrentUserResponsesSchema = {
|
||||
}))
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取用户列表接口响应组合
|
||||
* @description 用于Controller中定义所有可能的响应格式
|
||||
*/
|
||||
export const GetUserListResponsesSchema = {
|
||||
200: responseWrapperSchema(UserListResponseSchema),
|
||||
401: responseWrapperSchema(t.Object({
|
||||
error: t.String({
|
||||
description: '认证失败',
|
||||
examples: ['未提供有效的认证令牌', '令牌已过期']
|
||||
})
|
||||
})),
|
||||
403: responseWrapperSchema(t.Object({
|
||||
error: t.String({
|
||||
description: '权限不足',
|
||||
examples: ['权限不足,无法访问用户列表']
|
||||
})
|
||||
})),
|
||||
400: responseWrapperSchema(t.Object({
|
||||
error: t.String({
|
||||
description: '参数错误',
|
||||
examples: ['分页参数无效', '搜索关键词格式错误']
|
||||
})
|
||||
})),
|
||||
500: responseWrapperSchema(t.Object({
|
||||
error: t.String({
|
||||
description: '服务器错误',
|
||||
examples: ['内部服务器错误']
|
||||
})
|
||||
}))
|
||||
};
|
||||
|
||||
/** 获取当前用户信息成功响应数据类型 */
|
||||
export type GetCurrentUserSuccessType = Static<typeof GetCurrentUserResponsesSchema[200]>;
|
||||
|
||||
/** 获取用户列表成功响应数据类型 */
|
||||
export type GetUserListSuccessType = Static<typeof GetUserListResponsesSchema[200]>;
|
@ -4,10 +4,11 @@
|
||||
* @date 2024-12-19
|
||||
* @lastEditor AI Assistant
|
||||
* @lastEditTime 2025-01-07
|
||||
* @description 定义用户模块的Schema,包括获取当前用户信息等
|
||||
* @description 定义用户模块的Schema,包括获取当前用户信息、用户列表查询等
|
||||
*/
|
||||
|
||||
import { t, type Static } from 'elysia';
|
||||
import { createPaginationResponseSchema, createQuerySchema } from '@/utils/pagination';
|
||||
|
||||
/**
|
||||
* 当前用户信息响应Schema
|
||||
@ -40,7 +41,7 @@ export const CurrentUserSchema = t.Object({
|
||||
examples: ['https://example.com/avatar.jpg', null]
|
||||
}),
|
||||
/** 手机号 */
|
||||
mobile: t.Union([t.String(), t.Null()], {
|
||||
phone: t.Union([t.String(), t.Null()], {
|
||||
description: '手机号码',
|
||||
examples: ['13800138000', null]
|
||||
}),
|
||||
@ -66,5 +67,149 @@ export const CurrentUserSchema = t.Object({
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* 用户列表查询参数Schema
|
||||
* @description 用户列表查询的请求参数验证规则
|
||||
*/
|
||||
export const UserListQuerySchema = createQuerySchema(t.Object({
|
||||
// 用户特有参数
|
||||
keyword: t.Optional(t.String({
|
||||
minLength: 1,
|
||||
maxLength: 100,
|
||||
description: '搜索关键词,支持用户名、邮箱模糊搜索',
|
||||
examples: ['admin', 'test@example.com']
|
||||
})),
|
||||
status: t.Optional(t.Union([
|
||||
t.Literal('active'),
|
||||
t.Literal('inactive'),
|
||||
t.Literal('pending')
|
||||
], {
|
||||
description: '用户状态筛选',
|
||||
examples: ['active', 'inactive', 'pending']
|
||||
})),
|
||||
gender: t.Optional(t.Union([
|
||||
t.Literal(0),
|
||||
t.Literal(1),
|
||||
t.Literal(2),
|
||||
t.Literal('0'),
|
||||
t.Literal('1'),
|
||||
t.Literal('2'),
|
||||
], {
|
||||
description: '性别筛选:0-未知,1-男,2-女',
|
||||
examples: [0, 1, 2]
|
||||
})),
|
||||
isRoot: t.Optional(t.Boolean({
|
||||
description: '是否超级管理员筛选',
|
||||
examples: [true, false]
|
||||
}))
|
||||
}));
|
||||
|
||||
/**
|
||||
* 用户列表项Schema
|
||||
* @description 用户列表中单个用户的数据结构
|
||||
*/
|
||||
export const UserListItemSchema = t.Object({
|
||||
/** 用户ID */
|
||||
id: t.String({
|
||||
description: '用户ID(bigint类型以字符串形式返回防止精度丢失)',
|
||||
examples: ['1', '2', '3']
|
||||
}),
|
||||
/** 用户名 */
|
||||
username: t.String({
|
||||
description: '用户名',
|
||||
examples: ['admin', 'testuser']
|
||||
}),
|
||||
/** 邮箱地址 */
|
||||
email: t.String({
|
||||
description: '邮箱地址',
|
||||
examples: ['user@example.com']
|
||||
}),
|
||||
/** 手机号 */
|
||||
mobile: t.Union([t.String(), t.Null()], {
|
||||
description: '手机号码',
|
||||
examples: ['13800138000', null]
|
||||
}),
|
||||
/** 昵称 */
|
||||
nickname: t.Union([t.String(), t.Null()], {
|
||||
description: '用户昵称',
|
||||
examples: ['管理员', '测试用户', null]
|
||||
}),
|
||||
/** 头像URL */
|
||||
avatar: t.Union([t.String(), t.Null()], {
|
||||
description: '用户头像URL',
|
||||
examples: ['https://example.com/avatar.jpg', null]
|
||||
}),
|
||||
/** 账号状态 */
|
||||
status: t.String({
|
||||
description: '账号状态',
|
||||
examples: ['active', 'inactive', 'pending']
|
||||
}),
|
||||
/** 性别 */
|
||||
gender: t.Union([t.Number(), t.Null()], {
|
||||
description: '性别:0-未知,1-男,2-女',
|
||||
examples: [0, 1, 2, null]
|
||||
}),
|
||||
/** 生日 */
|
||||
birthday: t.Union([t.String(), t.Null()], {
|
||||
description: '生日',
|
||||
examples: ['1990-01-01', null]
|
||||
}),
|
||||
/** 个人简介 */
|
||||
bio: t.Union([t.String(), t.Null()], {
|
||||
description: '个人简介',
|
||||
examples: ['这是一段个人简介', null]
|
||||
}),
|
||||
/** 登录次数 */
|
||||
loginCount: t.Number({
|
||||
description: '登录次数',
|
||||
examples: [0, 10, 100]
|
||||
}),
|
||||
/** 最后登录时间 */
|
||||
lastLoginAt: t.Union([t.String(), t.Null()], {
|
||||
description: '最后登录时间',
|
||||
examples: ['2024-12-19T10:30:00Z', null]
|
||||
}),
|
||||
/** 最后登录IP */
|
||||
lastLoginIp: t.Union([t.String(), t.Null()], {
|
||||
description: '最后登录IP',
|
||||
examples: ['192.168.1.1', null]
|
||||
}),
|
||||
/** 失败尝试次数 */
|
||||
failedAttempts: t.Number({
|
||||
description: '失败尝试次数',
|
||||
examples: [0, 1, 5]
|
||||
}),
|
||||
/** 是否超级管理员 */
|
||||
isRoot: t.Boolean({
|
||||
description: '是否超级管理员',
|
||||
examples: [true, false]
|
||||
}),
|
||||
/** 创建时间 */
|
||||
createdAt: t.String({
|
||||
description: '创建时间',
|
||||
examples: ['2024-12-19T10:30:00Z']
|
||||
}),
|
||||
/** 更新时间 */
|
||||
updatedAt: t.String({
|
||||
description: '更新时间',
|
||||
examples: ['2024-12-19T10:30:00Z']
|
||||
})
|
||||
});
|
||||
|
||||
/**
|
||||
* 用户列表响应Schema
|
||||
* @description 用户列表查询的响应数据结构
|
||||
*/
|
||||
export const UserListResponseSchema = createPaginationResponseSchema(UserListItemSchema);
|
||||
|
||||
/** 当前用户信息响应类型 */
|
||||
export type CurrentUserResponse = Static<typeof CurrentUserSchema>;
|
||||
|
||||
/** 用户列表查询参数类型 */
|
||||
export type UserListQueryRequest = Static<typeof UserListQuerySchema>;
|
||||
|
||||
/** 用户列表项类型 */
|
||||
export type UserListItem = Static<typeof UserListItemSchema>;
|
||||
|
||||
/** 用户列表响应类型 */
|
||||
export type UserListResponse = Static<typeof UserListResponseSchema>;
|
@ -4,15 +4,18 @@
|
||||
* @date 2024-12-19
|
||||
* @lastEditor AI Assistant
|
||||
* @lastEditTime 2025-01-07
|
||||
* @description 用户模块的业务逻辑实现,包括获取当前用户信息等
|
||||
* @description 用户模块的业务逻辑实现,包括获取当前用户信息、用户列表查询等
|
||||
*/
|
||||
|
||||
import { Logger } from '@/plugins/logger/logger.service';
|
||||
import { db } from '@/plugins/drizzle/drizzle.service';
|
||||
import { sysUsers } from '@/eneities';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { eq, like, and, desc, asc, sql } from 'drizzle-orm';
|
||||
import { successResponse, errorResponse, BusinessError } from '@/utils/responseFormate';
|
||||
import type { GetCurrentUserSuccessType } from './user.response';
|
||||
|
||||
import { calculatePagination, normalizePaginationParams } from '@/utils/pagination';
|
||||
import type { GetCurrentUserSuccessType, GetUserListSuccessType } from './user.response';
|
||||
import type { UserListQueryRequest, UserListItem } from './user.schema';
|
||||
|
||||
/**
|
||||
* 用户服务类
|
||||
@ -59,18 +62,133 @@ export class UserService {
|
||||
Logger.info(`获取用户信息成功:${userId} - ${userData.username}`);
|
||||
|
||||
return successResponse({
|
||||
id: userData.id!.toString(),
|
||||
id: userId, // 使用传入的字符串ID,避免精度丢失
|
||||
username: userData.username,
|
||||
email: userData.email,
|
||||
nickname: userData.nickname,
|
||||
avatar: userData.avatar,
|
||||
mobile: userData.mobile,
|
||||
phone: userData.mobile,
|
||||
status: userData.status,
|
||||
lastLoginAt: userData.lastLoginAt || null,
|
||||
createdAt: userData.createdAt,
|
||||
updatedAt: userData.updatedAt
|
||||
}, '获取用户信息成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param query 查询参数
|
||||
* @returns Promise<GetUserListSuccessType>
|
||||
* @throws BusinessError 业务逻辑错误
|
||||
* @type API =====================================================================
|
||||
*/
|
||||
public async getUserList(query: UserListQueryRequest): Promise<GetUserListSuccessType> {
|
||||
// 标准化分页参数
|
||||
const { page, pageSize, sortBy, sortOrder } = normalizePaginationParams(query);
|
||||
const { keyword, status, gender, isRoot } = query;
|
||||
|
||||
// 构建查询条件
|
||||
const conditions = [];
|
||||
|
||||
// 关键词搜索(用户名、邮箱模糊搜索)
|
||||
if (keyword) {
|
||||
conditions.push(
|
||||
sql`(${sysUsers.username} LIKE ${`%${keyword}%`} OR ${sysUsers.email} LIKE ${`%${keyword}%`})`
|
||||
);
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
conditions.push(eq(sysUsers.status, status));
|
||||
}
|
||||
|
||||
// 性别筛选
|
||||
if (gender !== undefined) {
|
||||
conditions.push(eq(sysUsers.gender, gender));
|
||||
}
|
||||
|
||||
// 超级管理员筛选
|
||||
if (isRoot !== undefined) {
|
||||
conditions.push(eq(sysUsers.isRoot, isRoot ? 1 : 0));
|
||||
}
|
||||
|
||||
// 只查询未删除的用户
|
||||
conditions.push(sql`${sysUsers.deletedAt} IS NULL`);
|
||||
|
||||
// 构建排序
|
||||
const orderBy = sortBy === 'username' ? sysUsers.username :
|
||||
sortBy === 'email' ? sysUsers.email :
|
||||
sortBy === 'updatedAt' ? sysUsers.updatedAt :
|
||||
sysUsers.createdAt;
|
||||
|
||||
const orderDirection = sortOrder === 'asc' ? asc : desc;
|
||||
|
||||
// 查询总数
|
||||
const countResult = await db()
|
||||
.select({ count: sql<number>`count(${sysUsers.id})` })
|
||||
.from(sysUsers)
|
||||
.where(and(...conditions));
|
||||
|
||||
const total = Number(countResult[0]?.count || 0);
|
||||
|
||||
// 查询数据
|
||||
const users = await db()
|
||||
.select({
|
||||
id: sysUsers.id,
|
||||
username: sysUsers.username,
|
||||
email: sysUsers.email,
|
||||
mobile: sysUsers.mobile,
|
||||
nickname: sysUsers.nickname,
|
||||
avatar: sysUsers.avatar,
|
||||
status: sysUsers.status,
|
||||
gender: sysUsers.gender,
|
||||
birthday: sysUsers.birthday,
|
||||
bio: sysUsers.bio,
|
||||
loginCount: sysUsers.loginCount,
|
||||
lastLoginAt: sysUsers.lastLoginAt,
|
||||
lastLoginIp: sysUsers.lastLoginIp,
|
||||
failedAttempts: sysUsers.failedAttempts,
|
||||
isRoot: sysUsers.isRoot,
|
||||
createdAt: sysUsers.createdAt,
|
||||
updatedAt: sysUsers.updatedAt
|
||||
})
|
||||
.from(sysUsers)
|
||||
.where(and(...conditions))
|
||||
.orderBy(orderDirection(orderBy))
|
||||
.limit(pageSize)
|
||||
.offset((page - 1) * pageSize);
|
||||
|
||||
// 转换数据格式
|
||||
const userList: UserListItem[] = users.map(user => ({
|
||||
id: user.id!.toString(), // 确保ID以字符串形式返回
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
mobile: user.mobile,
|
||||
nickname: user.nickname,
|
||||
avatar: user.avatar,
|
||||
status: user.status,
|
||||
gender: user.gender,
|
||||
birthday: user.birthday,
|
||||
bio: user.bio,
|
||||
loginCount: user.loginCount,
|
||||
lastLoginAt: user.lastLoginAt || null,
|
||||
lastLoginIp: user.lastLoginIp,
|
||||
failedAttempts: user.failedAttempts,
|
||||
isRoot: user.isRoot === 1,
|
||||
createdAt: user.createdAt,
|
||||
updatedAt: user.updatedAt
|
||||
}));
|
||||
|
||||
// 计算分页信息
|
||||
const pagination = calculatePagination(total, page, pageSize);
|
||||
|
||||
Logger.info(`获取用户列表成功:总数${total},当前页${page},每页${pageSize}条`);
|
||||
|
||||
return successResponse({
|
||||
...pagination,
|
||||
data: userList
|
||||
}, '获取用户列表成功');
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
|
129
src/utils/pagination.ts
Normal file
129
src/utils/pagination.ts
Normal file
@ -0,0 +1,129 @@
|
||||
/**
|
||||
* @file 分页工具
|
||||
* @author AI Assistant
|
||||
* @date 2024-12-19
|
||||
* @lastEditor AI Assistant
|
||||
* @lastEditTime 2025-01-07
|
||||
* @description 分页查询相关的Schema和工具函数
|
||||
*/
|
||||
|
||||
import { t, type Static, type TSchema } from 'elysia';
|
||||
|
||||
/**
|
||||
* 基础分页查询参数Schema
|
||||
*/
|
||||
export const BasePaginationSchema = t.Object({
|
||||
/** 页码,从1开始 */
|
||||
page: t.Optional(t.Number({
|
||||
minimum: 1,
|
||||
description: '页码,从1开始',
|
||||
examples: [1, 2, 3],
|
||||
default: 1
|
||||
})),
|
||||
/** 每页大小,最大100 */
|
||||
pageSize: t.Optional(t.Number({
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
description: '每页大小,最大100',
|
||||
examples: [10, 20, 50],
|
||||
default: 20
|
||||
})),
|
||||
/** 排序字段 */
|
||||
sortBy: t.Optional(t.String({
|
||||
description: '排序字段',
|
||||
examples: ['createdAt', 'updatedAt', 'username', 'email'],
|
||||
default: 'createdAt'
|
||||
})),
|
||||
/** 排序方向 */
|
||||
sortOrder: t.Optional(t.Union([
|
||||
t.Literal('asc'),
|
||||
t.Literal('desc')
|
||||
], {
|
||||
description: '排序方向',
|
||||
examples: ['asc', 'desc'],
|
||||
default: 'desc'
|
||||
}))
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建带分页的查询Schema
|
||||
* @description 将分页参数和自定义参数合并为一个Schema,避免t.Intersect的问题
|
||||
* @param customSchema 自定义参数Schema
|
||||
* @returns 合并后的Schema
|
||||
*/
|
||||
export const createQuerySchema = (customSchema: any) => {
|
||||
return t.Object({
|
||||
...BasePaginationSchema.properties,
|
||||
...customSchema.properties
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础分页查询参数类型
|
||||
*/
|
||||
export type BasePaginationRequest = Static<typeof BasePaginationSchema>;
|
||||
|
||||
/**
|
||||
* 分页响应数据Schema
|
||||
* @param dataSchema 数据项的Schema
|
||||
*/
|
||||
export const createPaginationResponseSchema = <T>(dataSchema: T) => {
|
||||
return t.Object({
|
||||
/** 总记录数 */
|
||||
total: t.Number({
|
||||
description: '总记录数',
|
||||
examples: [100, 250, 1000]
|
||||
}),
|
||||
/** 当前页码 */
|
||||
page: t.Number({
|
||||
description: '当前页码',
|
||||
examples: [1, 2, 3]
|
||||
}),
|
||||
/** 每页大小 */
|
||||
pageSize: t.Number({
|
||||
description: '每页大小',
|
||||
examples: [10, 20, 50]
|
||||
}),
|
||||
/** 数据列表 */
|
||||
data: t.Array(dataSchema)
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 分页响应数据类型
|
||||
*/
|
||||
export type PaginationResponse<T> = {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
data: T[];
|
||||
};
|
||||
|
||||
/**
|
||||
* 计算分页信息
|
||||
* @param total 总记录数
|
||||
* @param page 当前页码
|
||||
* @param pageSize 每页大小
|
||||
* @returns 分页信息
|
||||
*/
|
||||
export const calculatePagination = (total: number, page: number, pageSize: number) => {
|
||||
return {
|
||||
total,
|
||||
page,
|
||||
pageSize
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 验证和标准化分页参数
|
||||
* @param params 原始参数
|
||||
* @returns 标准化后的参数
|
||||
*/
|
||||
export const normalizePaginationParams = (params: Partial<BasePaginationRequest>): Required<BasePaginationRequest> => {
|
||||
return {
|
||||
page: Math.max(1, params.page || 1),
|
||||
pageSize: Math.min(100, Math.max(1, params.pageSize || 20)),
|
||||
sortBy: params.sortBy || 'createdAt',
|
||||
sortOrder: params.sortOrder || 'desc'
|
||||
};
|
||||
};
|
1
src/utils/schema.ts
Normal file
1
src/utils/schema.ts
Normal file
@ -0,0 +1 @@
|
||||
|
@ -124,21 +124,21 @@
|
||||
|
||||
### 👤 用户管理模块 (User Module) - P0优先级
|
||||
|
||||
- [ ] 9.0 GET /users/me - 获取当前用户信息接口
|
||||
- [ ] Before 整理输入此接口的逻辑,必须等待用户确认后进行,需要输入go才能进行下一步
|
||||
- [ ] 9.1 扩展user.schema.ts - 定义当前用户Schema
|
||||
- [ ] 9.2 扩展user.response.ts - 定义当前用户响应格式
|
||||
- [ ] 9.3 扩展user.service.ts - 实现当前用户业务逻辑
|
||||
- [ ] 9.4 更新user.controller.ts - 实现当前用户路由
|
||||
- [ ] 9.5 创建user.test.md - 编写当前用户测试用例文档
|
||||
- [x] 9.0 GET /users/me - 获取当前用户信息接口
|
||||
- [x] Before 整理输入此接口的逻辑,必须等待用户确认后进行,需要输入go才能进行下一步
|
||||
- [x] 9.1 扩展user.schema.ts - 定义当前用户Schema
|
||||
- [x] 9.2 扩展user.response.ts - 定义当前用户响应格式
|
||||
- [x] 9.3 扩展user.service.ts - 实现当前用户业务逻辑
|
||||
- [x] 9.4 更新user.controller.ts - 实现当前用户路由
|
||||
- [x] 9.5 创建user.test.md - 编写当前用户测试用例文档
|
||||
|
||||
- [ ] 10.0 GET /users - 用户列表查询接口
|
||||
- [ ] Before 整理输入此接口的逻辑,必须等待用户确认后进行,需要输入go才能进行下一步
|
||||
- [ ] 10.1 扩展user.schema.ts - 定义用户列表Schema
|
||||
- [ ] 10.2 扩展user.response.ts - 定义用户列表响应格式
|
||||
- [ ] 10.3 扩展user.service.ts - 实现用户列表业务逻辑
|
||||
- [ ] 10.4 扩展user.controller.ts - 实现用户列表路由
|
||||
- [ ] 10.5 扩展user.test.md - 编写用户列表测试用例文档
|
||||
- [x] 10.0 GET /users - 用户列表查询接口
|
||||
- [x] Before 整理输入此接口的逻辑,必须等待用户确认后进行,需要输入go才能进行下一步
|
||||
- [x] 10.1 扩展user.schema.ts - 定义用户列表Schema
|
||||
- [x] 10.2 扩展user.response.ts - 定义用户列表响应格式
|
||||
- [x] 10.3 扩展user.service.ts - 实现用户列表业务逻辑
|
||||
- [x] 10.4 扩展user.controller.ts - 实现用户列表路由
|
||||
- [x] 10.5 扩展user.test.md - 编写用户列表测试用例文档
|
||||
|
||||
- [ ] 11.0 POST /users - 创建用户接口
|
||||
- [ ] Before 整理输入此接口的逻辑,必须等待用户确认后进行,需要输入go才能进行下一步
|
||||
|
Loading…
Reference in New Issue
Block a user