Skip to content

工具函数

server/utils/ 目录下的文件自动导入为服务端工具函数,在整个 server/ 目录中可以直接使用,无需手动 import。

自动导入机制

ts
// server/utils/database.ts
export const createDb = () => {
  return drizzle(process.env.DATABASE_URL!)
}

在服务端任意位置直接使用,无需 import:

ts
// server/api/users.ts
export default defineEventHandler(async () => {
  const db = createDb()  // 自动导入!
  return await db.select().from(users)
})

自动导入的原理

Nitro 扫描 server/utils/ 目录下的所有导出,将它们注册为自动导入。和 Nuxt 的客户端自动导入类似,但只作用于 server/ 目录。

自动导入的范围

server/utils/ 中的导出只在 server/ 目录内可用,客户端代码无法使用。反之亦然。

#server 别名

在深层嵌套的服务端文件中,使用 #server 别名代替繁琐的相对路径:

ts
// server/api/admin/users/roles.ts

// ❌ 相对路径——层级深时难以维护
import { isAdmin } from '../../../utils/auth'

// ✅ #server 别名——清晰明确
import { isAdmin } from '#server/utils/auth'

#server 的适用范围

只能在 server/ 目录内使用。客户端代码中不存在 #server

何时用 #server

当你的工具函数在深层嵌套的目录中,相对路径超过 2 层时,推荐用 #server

常用工具函数封装

响应封装

统一 API 响应格式,让前端处理更一致:

ts
// server/utils/response.ts
export const successResponse = <T>(data: T, message = 'ok') => {
  return { code: 0, message, data }
}

export const errorResponse = (message: string, code = -1) => {
  return { code, message, data: null }
}

export const paginatedResponse = <T>(
  list: T[],
  total: number,
  page: number,
  pageSize: number
) => {
  return {
    code: 0,
    message: 'ok',
    data: {
      list,
      pagination: {
        total,
        page,
        pageSize,
        totalPages: Math.ceil(total / pageSize),
      },
    },
  }
}
ts
// server/api/users.ts
export default defineEventHandler(async () => {
  const users = await getUsers()
  return successResponse(users)
})

// server/api/users.ts(分页)
export default defineEventHandler(async (event) => {
  const { page = 1, size = 20 } = getQuery(event)
  const { list, total } = await getPaginatedUsers(page, size)
  return paginatedResponse(list, total, page, size)
})

统一响应格式的好处

  • 前端可以统一处理 code 判断成功/失败
  • 分页数据格式一致,避免每个接口重复定义
  • 错误消息格式统一,便于展示

JWT 工具

ts
// server/utils/jwt.ts
import jwt from 'jsonwebtoken'

export const signToken = (payload: object) => {
  return jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn: '7d' })
}

export const verifyToken = (token: string) => {
  return jwt.verify(token, process.env.JWT_SECRET!) as { userId: number }
}

export const getTokenFromHeader = (event: H3Event) => {
  const auth = getRequestHeader(event, 'authorization')
  if (!auth?.startsWith('Bearer ')) {
    throw createError({ statusCode: 401, statusMessage: 'Unauthorized', message: 'Missing token' })
  }
  return verifyToken(auth.replace('Bearer ', ''))
}
ts
// server/api/me.ts
export default defineEventHandler((event) => {
  const { userId } = getTokenFromHeader(event)
  return getUserById(userId)
})

密码工具

ts
// server/utils/password.ts
import bcrypt from 'bcryptjs'

export const hashPassword = (password: string) => {
  return bcrypt.hash(password, 10)
}

export const verifyPassword = (password: string, hash: string) => {
  return bcrypt.compare(password, hash)
}
ts
// server/api/auth/register.ts
export default defineEventHandler(async (event) => {
  const { name, email, password } = await readBody(event)

  const hashedPassword = await hashPassword(password)
  const user = await createUser({ name, email, password: hashedPassword })

  return { id: user.id, name: user.name }
})

分页工具

ts
// server/utils/pagination.ts
export interface PaginationResult<T> {
  list: T[]
  total: number
  page: number
  pageSize: number
  totalPages: number
}

export const paginate = async <T>(
  query: any,
  page = 1,
  pageSize = 20
): Promise<PaginationResult<T>> => {
  const offset = (page - 1) * pageSize
  const [list, totalResult] = await Promise.all([
    query.offset(offset).limit(pageSize),
    query.count(),
  ])

  return {
    list,
    total: totalResult,
    page,
    pageSize,
    totalPages: Math.ceil(totalResult / pageSize),
  }
}

// 从请求中获取分页参数
export const getPaginationParams = (event: H3Event) => {
  const query = getQuery(event)
  return {
    page: Math.max(1, Number(query.page) || 1),
    pageSize: Math.min(100, Math.max(1, Number(query.size) || 20)),
  }
}
ts
// server/api/users.ts
export default defineEventHandler(async (event) => {
  const { page, pageSize } = getPaginationParams(event)
  const result = await paginate(db.select().from(users), page, pageSize)
  return result
})

getPaginationParams 的安全处理

  • Math.max(1, ...) → page 最小为 1
  • Math.min(100, ...) → pageSize 最大为 100(防止一次查询太多数据)
  • Math.max(1, ...) → pageSize 最小为 1

权限检查工具

ts
// server/utils/auth.ts
export const requireAuth = (event: H3Event) => {
  if (!event.context.user) {
    throw createError({ statusCode: 401, statusMessage: 'Unauthorized', message: '未认证' })
  }
  return event.context.user
}

export const requireAdmin = (event: H3Event) => {
  const user = requireAuth(event)
  if (user.role !== 'admin') {
    throw createError({ statusCode: 403, statusMessage: 'Forbidden', message: '需要管理员权限' })
  }
  return user
}

export const requireOwner = (event: H3Event, resourceUserId: number) => {
  const user = requireAuth(event)
  if (user.role !== 'admin' && user.id !== resourceUserId) {
    throw createError({ statusCode: 403, statusMessage: 'Forbidden', message: '需要资源所有者或管理员权限' })
  }
  return user
}
ts
// server/api/admin/users.ts
export default defineEventHandler((event) => {
  requireAdmin(event)  // 只允许管理员
  return getAllUsers()
})

// server/api/users/[id].ts
export default defineEventHandler((event) => {
  const user = requireAuth(event)
  const id = getRouterParam(event, 'id')
  requireOwner(event, Number(id))  // 只允许本人或管理员
  return getUserById(id)
})

H3 内置工具函数

H3 是 Nuxt 服务端的底层框架,提供了丰富的工具函数:

请求相关

函数说明示例
readBody(event)读取请求体(JSON 自动解析)const body = await readBody(event)
readValidatedBody(event, schema)校验请求体const body = await readValidatedBody(event, schema.parse)
getQuery(event)获取查询参数const { page } = getQuery(event)
getValidatedQuery(event, schema)校验查询参数const query = await getValidatedQuery(event, schema.parse)
getRouterParam(event, name)获取路由参数const id = getRouterParam(event, 'id')
getRequestHeader(event, name)获取请求头const auth = getRequestHeader(event, 'authorization')
getRequestURL(event)获取请求 URLconst url = getRequestURL(event)
getCookie(event, name)获取 Cookieconst token = getCookie(event, 'auth-token')

响应相关

函数说明示例
setResponseStatus(event, code)设置状态码setResponseStatus(event, 201)
setResponseHeader(event, name, value)设置响应头setResponseHeader(event, 'x-total', '100')
sendRedirect(event, url, code)重定向await sendRedirect(event, '/login', 302)
setCookie(event, name, value, opts)设置 CookiesetCookie(event, 'token', value, { httpOnly: true })
deleteCookie(event, name, opts)删除 CookiedeleteCookie(event, 'token')
sendStream(event, stream)发送流return sendStream(event, createReadStream(path))

这些函数都是自动导入的

无需手动 import。直接在服务端代码中使用即可。

知识脉络

text
API 路由 → 服务路由 → 服务中间件 → 服务插件 → 你在这里:工具函数

                                              ├─→ 下一步:事件处理

                                              └─→ 相关:事件处理(H3 工具函数详解)

基于 Nuxt 4 官方文档整理编写