You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

8.8 KiB

BaseController 使用指南

BaseController 是项目的基础控制器类,提供了一套完整的 Web 开发常用功能,包括错误处理、参数验证、分页、权限检查等。所有控制器都应该继承此类以保持代码的一致性和可维护性。

特性概览

  • 🛡️ 统一异常处理 - 自动捕获和格式化错误响应
  • 参数验证 - 内置常用参数验证规则
  • 📄 分页支持 - 标准化分页参数处理
  • 🔐 权限控制 - 用户权限和资源所有权检查
  • 📁 文件上传 - 文件上传处理助手
  • 🎨 响应格式化 - 统一的 JSON 和视图响应

基本用法

1. 继承 BaseController

import BaseController from \"@/base/BaseController.js\"
import Router from \"utils/router.js\"
import YourService from \"services/YourService.js\"

class YourController extends BaseController {
    constructor() {
        super() // 必须调用 super()
        this.yourService = new YourService()
    }

    // 控制器方法
    async yourMethod(ctx) {
        // 业务逻辑
    }

    // 路由定义
    static createRoutes() {
        const controller = new YourController()
        const router = new Router({ prefix: '/api/your-resource' })
        
        router.get('/', controller.handleRequest(controller.yourMethod))
        
        return router
    }
}

2. 使用异常处理装饰器

推荐方式:使用 handleRequest 包装控制器方法,自动处理异常:

// 路由注册时使用 handleRequest
router.get('/', controller.handleRequest(controller.yourMethod))

// 控制器方法正常编写,无需 try-catch
async yourMethod(ctx) {
    const data = await this.yourService.getData()
    return this.success(ctx, data, \"获取数据成功\")
}

手动方式:如果需要自定义异常处理:

async yourMethod(ctx) {
    try {
        const data = await this.yourService.getData()
        return this.success(ctx, data, \"获取数据成功\")
    } catch (error) {
        if (error instanceof CommonError) {
            return this.error(ctx, error.message, null, 400)
        }
        return this.error(ctx, \"系统内部错误\", null, 500)
    }
}

核心方法详解

响应方法

success(ctx, data, message, statusCode)

生成成功响应

return this.success(ctx, { id: 1, name: 'test' }, \"操作成功\", 200)
// 响应: { success: true, data: {...}, error: \"操作成功\" }

error(ctx, message, data, statusCode)

生成错误响应

return this.error(ctx, \"数据不存在\", null, 404)
// 响应: { success: false, data: null, error: \"数据不存在\" }

paginated(ctx, paginationResult, message)

生成分页响应

const result = await this.service.getDataWithPagination(page, limit)
return this.paginated(ctx, result, \"获取列表成功\")
// 响应: { success: true, data: { list: [...], pagination: {...} } }

参数处理

validateParams(ctx, rules)

验证请求参数(支持 body、query、params)

const data = this.validateParams(ctx, {
    title: { 
        required: true, 
        minLength: 1, 
        maxLength: 100, 
        label: '标题' 
    },
    email: { 
        required: false, 
        type: 'email', 
        label: '邮箱' 
    },
    age: { 
        required: true, 
        type: 'number', 
        label: '年龄' 
    }
})

验证规则说明

  • required: 是否必填
  • type: 数据类型('number', 'email')
  • minLength/maxLength: 字符串长度限制
  • label: 字段显示名称(用于错误消息)

getPaginationParams(ctx, defaults)

获取分页参数

const params = this.getPaginationParams(ctx, {
    page: 1,
    limit: 20,
    orderBy: 'created_at',
    order: 'desc'
})
// 返回: { page: 1, limit: 20, orderBy: 'created_at', order: 'desc' }

getSearchParams(ctx)

获取搜索参数

const params = this.getSearchParams(ctx)
// 从 query 中提取: keyword, status, category, author

权限控制

getCurrentUser(ctx)

获取当前登录用户

const user = this.getCurrentUser(ctx)
if (!user) {
    throw new CommonError(\"用户未登录\")
}

checkPermission(ctx, permission)

检查用户权限(需要根据业务实现权限逻辑)

this.checkPermission(ctx, 'article.create')

checkOwnership(ctx, resource, ownerField)

检查资源所有权

const article = await this.articleService.getById(id)
this.checkOwnership(ctx, article, 'author') // 检查 article.author 是否为当前用户

视图和文件处理

render(ctx, template, data, options)

渲染视图模板

return this.render(ctx, 'articles/detail', {
    article,
    title: article.title
}, {
    includeSite: true,
    includeUser: true
})

redirect(ctx, url, message)

页面重定向

this.redirect(ctx, '/articles', '文章创建成功')

getUploadedFile(ctx, fieldName)

处理文件上传

const file = this.getUploadedFile(ctx, 'avatar')
if (file) {
    console.log('文件名:', file.name)
    console.log('文件大小:', file.size)
    console.log('文件类型:', file.type)
    console.log('文件路径:', file.path)
}

完整示例

import BaseController from \"@/base/BaseController.js\"
import Router from \"utils/router.js\"
import ArticleService from \"services/ArticleService.js\"

class ArticleController extends BaseController {
    constructor() {
        super()
        this.articleService = new ArticleService()
    }

    // 获取文章列表(支持分页和搜索)
    async getArticles(ctx) {
        const searchParams = this.getSearchParams(ctx)
        const paginationParams = this.getPaginationParams(ctx)

        const result = await this.articleService.getArticlesWithPagination(
            paginationParams.page,
            paginationParams.limit,
            searchParams.status
        )

        return this.paginated(ctx, result, \"获取文章列表成功\")
    }

    // 创建文章
    async createArticle(ctx) {
        // 权限检查
        this.checkPermission(ctx, 'article.create')

        // 参数验证
        const data = this.validateParams(ctx, {
            title: { required: true, minLength: 1, maxLength: 200, label: '标题' },
            content: { required: true, minLength: 10, label: '内容' }
        })

        // 添加作者信息
        const user = this.getCurrentUser(ctx)
        data.author = user.id

        const article = await this.articleService.createArticle(data)
        return this.success(ctx, article, \"创建文章成功\", 201)
    }

    // 更新文章
    async updateArticle(ctx) {
        const { id } = this.validateParams(ctx, {
            id: { required: true, type: 'number', label: '文章ID' }
        })

        // 检查文章是否存在和所有权
        const article = await this.articleService.getArticleById(id)
        this.checkOwnership(ctx, article)

        // 验证更新数据
        const updateData = this.validateParams(ctx, {
            title: { required: false, minLength: 1, maxLength: 200, label: '标题' },
            content: { required: false, minLength: 10, label: '内容' }
        })

        const updatedArticle = await this.articleService.updateArticle(id, updateData)
        return this.success(ctx, updatedArticle, \"更新文章成功\")
    }

    // 路由定义
    static createRoutes() {
        const controller = new ArticleController()
        const router = new Router({ prefix: '/api/articles' })

        router.get('/', controller.handleRequest(controller.getArticles), { auth: false })
        router.post('/', controller.handleRequest(controller.createArticle), { auth: true })
        router.put('/:id', controller.handleRequest(controller.updateArticle), { auth: true })

        return router
    }
}

export default ArticleController

最佳实践

1. 统一错误处理

  • 使用 handleRequest 包装所有控制器方法
  • 业务异常抛出 CommonError
  • 让 BaseController 自动处理系统异常

2. 参数验证

  • 总是验证用户输入
  • 使用有意义的字段标签
  • 根据业务需求设置合适的验证规则

3. 权限控制

  • 在需要的地方调用 checkPermission
  • 对用户资源使用 checkOwnership
  • 在路由级别设置基础权限要求

4. 响应格式

  • 使用统一的响应方法
  • 提供有意义的消息
  • 分页数据使用 paginated 方法

5. 代码组织

  • 保持控制器方法简洁
  • 业务逻辑放在 Service 层
  • 使用静态 createRoutes 方法定义路由

通过遵循这些模式,您可以创建一致、可维护且健壮的控制器代码。