6.5 添加新功能与数据库迁移

添加新功能与数据库迁移

新功能沿现有分层实现,并从一开始处理工作区隔离、权限、迁移和测试。不要先把逻辑堆进 Controller 再补架构。

实现顺序

  1. app/model 定义实体、状态常量和表名。
  2. app/dto 定义请求/响应及校验标签。
  3. app/dao 编写带 workspace_id 的查询。
  4. app/service 实现业务规则和事务。
  5. app/controller 绑定输入、检查角色、映射错误和返回统一响应。
  6. config/autoload/router.go 注册明确的 HTTP 方法和路径。
  7. 为三种数据库添加同版本迁移,并补测试与文档。
  8. 在管理端 src/api 增加类型化客户端,再实现页面。

Controller 示例

func (ctrl WidgetController) Create(c httpInterfaces.RouterContextInterface) {
    if !middleware.CanManageBusinessResource(c) {
        ctrl.Error(c, constants.ErrCodeForbidden, "无权限创建资源")
        return
    }
    var req dto.CreateWidgetRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        ctrl.Error(c, constants.ErrCodeBadRequest, "请求参数错误: "+err.Error())
        return
    }
    result, err := service.NewWidgetService(helper.GetHelper()).Create(
        middleware.GetCurrentWorkspaceID(c),
        middleware.GetCurrentUserID(c),
        &req,
    )
    if err != nil {
        ctrl.Error(c, constants.ErrCodeInternal, err.Error())
        return
    }
    ctrl.Success(c, result)
}

真实代码应把可预期的“不存在”“冲突”“无权限”分别映射为 404、409、403,不要全部返回 500。

迁移约定

结构变更在三个目录使用相同版本号和等价语义:

migrations/mysql/2026072209000000_add_widgets.sql
migrations/postgresql/2026072209000000_add_widgets.sql
migrations/sqlite/2026072209000000_add_widgets.sql

每个 SQL 文件提供 Goose Up/Down 段。不要编辑已发布文件;新修复通过更高版本迁移完成。跨方言的数据回填确实需要 Go 逻辑时,可参考 migrations/0015_backfill_click_statistics_region.go 的注册方式,但需明确幂等、并发、多实例和失败恢复策略。

工作区与权限检查

  • DAO 的详情、更新和删除条件同时包含资源 ID 与 workspace_id
  • 读操作允许当前工作区成员;业务写操作通常允许 owner/admin/member;系统管理写操作只允许 owner/admin。
  • 返回 404 时避免泄露其他工作区存在同 ID 资源。
  • 后台菜单权限只是体验层,服务端必须独立检查。

缓存与副作用

数据库写成功后再失效缓存。异步统计或回填需要可观察的错误日志和幂等设计;不要让 Goroutine 捕获即将失效的请求对象。涉及短链跳转时同时验证安全策略、高级路由、A/B、Query 透传和点击记录。

完成标准

  • 三方言迁移可从空库和升级库执行;
  • Service 规则、权限和错误分支有测试;
  • go test ./...、前端 typecheck 和构建通过;
  • API 文档、前端类型与后端 DTO 一致;
  • 不记录或返回不必要的 Secret 和个人数据。

相关章节:架构测试