按 owl 体系创建高质量后端模块
本 Skill 与全局规则 coding-standards.mdc 配合使用。完整模板与验证步骤统一以 owl/docs/ 为准,动手前先读对应文档。
按需深入阅读(与本文件同目录):
- owl ServiceProvider 能力与用法 →
provider-reference.md - 后端 API 黑盒测试流程 →
api-testing-guide.md
统一路径(先读 docs 再动手)
本 Skill 只面向 新建独立子系统:新业务线、新仓库或新包,基于 owl 框架独立搭建后端服务。
一体化业务仓库(前后端同仓、目录不同级)
部分业务线把 Go SubApp 与前端子系统包放在同一 Git 仓库,与「后端仓库、前端仓库两个并列根目录」的旧布局不同。典型目录约定:
| 层级 | 路径(相对业务仓库根) | 说明 |
|---|---|---|
| 后端 | app/、conf/、go.mod 等 |
与独立 owl 子应用相同的分层与接线,无变化 |
| 前端 | frontend/<子前端目录>/(如 frontend/admin/) |
独立 npm 包:package.json、src/index.ts(defineSubsystem)、src/views/、src/api/ 等 |
生成或修改代码时:
- 以用户当前打开的仓库为根;后端文件落在
app/(及route/、database/等该仓库既有结构),不要写到与frontend/同级的另一个「假想的独立前端仓库根」。 - 若用户说明前端在
frontend/下,Agent 应把前端相关路径理解为frontend/<子应用>/src/...,而不是 workspace 里与后端仓库并列的另一个文件夹。 - 风格参考仍可从
owl-admin等框架示例仓库用 Read 读取;落地路径写清楚业务仓库内的相对路径即可。
工作流:先读真实源码,再读 docs,最后动手
第一步:读真实参考源码(硬性要求)
在生成任何 service / repository / handle 代码之前,必须用 Read 工具阅读以下参考文件(不是凭记忆,不是靠文档片段):
| 层 | 参考文件(相对于 workspace 根目录) | 重点关注 |
|---|---|---|
| service | owl-admin/app/service/dict_service.go |
DTO struct 的 validate + label tag 写法、BizError 定义、锁、copier |
| service | owl-admin/app/service/role_service.go |
多 DTO(Create/Update/Assign)的 tag 风格对照 |
| handle | owl-admin/app/handle/v1/dict_handle.go |
Bind → Service → router.Success/Fail 的接线 |
| repository | owl-admin/app/repository/dict.go |
接口定义 + WithContext + 构造函数返回接口 |
为什么:文档里的代码片段可能不完整或过时,真实源码才是"唯一事实来源"。 阅读后提取出以下模式并在生成代码时严格遵循:
- struct tag 的完整写法(
json+validate+label三件套)- 业务错误码常量 + 构造函数的组织方式
- service 方法签名与返回值风格
- handle 层的 Bind / 响应模式
如果目标项目已有同类 service 文件(如 owl-portal/app/service/ 下已有文件),也应至少读 1-2 个已有文件以保持风格一致。
第二步:读框架文档
再读 owl/docs/05-create-new-subapp-playbook.md、owl/docs/07-minimal-subapp-template.md,了解子应用骨架与接线全貌。
第三步:生成代码 & 验证
按下方「后端分层与接线顺序」逐层生成,完成后按 owl/docs/08-startup-and-verification.md 验证。
后端分层与接线顺序(单资源)
- model:
db.BaseModel、TableName()、状态常量与 model 同包;所有会落库的字段(含仅有指针/默认列名、无size/index的字段)在gormtag 中必须含comment:中文简短说明(与size/index等写在同一 tag 内),与owl/docs/07-minimal-subapp-template.md示例一致;gorm:"-"等不参与落库的字段可省略。嵌入的db.BaseModel由框架定义,子应用自定义字段不得遗漏comment:。 - repository:接口 + 实现,
WithContext,构造函数返回接口类型。 - service:Create/UpdateReq、
validate标签、label:"中文名"标签、写操作用redis.LockerFactory加锁、copier.Copy到 model,调repo.WithContext(ctx)。凡带validate规则的字段,必须同时加label:"中文名"标签(用于验证错误的中文翻译),参照owl-admin现有 DTO 风格。 - handle:实现
router.Handler(ModuleName()),Bind → Service →router.Success/router.Fail/router.PageSuccess;对外 HTTP 方法需 swagger 注释。 - Binds():追加
NewXxxRepository、NewXxxService、NewXxxHandle(顺序建议 repo → service → handle)。 - route:
InitApi中注入 handle,用router.NewRouteInfoBuilder(...)注册路由,.Name("中文").Build();需菜单则xxxMenu = r.GetMenu()并在InitMenu()挂到父级。 - 迁移:
database/auto_migrate_gen.go的Migrate(db)中追加&Xxx{},Bootstrap()中调用database.Migrate(...)。
禁止:Handle 直接注入 Repository。即使是最简单的 CRUD,也必须经过 Service 层。
Service 是放校验、copier、锁、事件的唯一位置;Handle 只做 Bind + 调 Service + 返回响应。
文件与模块边界(硬约束)
- model:按业务域拆分为多个文件(如
site_config.go、navigation.go)。允许同一文件内放强相关的多个类型(例如Product与ProductCategory),禁止把所有实体堆进单个models.go。 - repository:每个资源一套
xxx_repository.go,包含 接口 + 实现 +WithContext,构造函数返回接口类型。禁止NewModel(module string)、switch module、反射拼装列表等“总线式”仓储。 - service:每个资源一套
xxx_service.go,含强类型CreateXxxReq/UpdateXxxReq/RetrieveXxxReq(及模块特有方法)。禁止用map[string]any作为对外 HTTP 入参载体替代上述结构体。 - handle:每个资源一套
xxx_handle.go(或v1/xxx_handle.go),ShouldBindJSON/ShouldBindQuery绑定到 service 的 Req。禁止单文件PortalHandle聚合全站 CRUD。 - route:可在
route/api.go集中注册,但每条路由必须调用对应资源的 Handle 方法;禁止用ctx.Set("module", ...)+ 通用Create/Update承载核心业务逻辑。
反例(一律不允许)
PortalRepository/PortalService/PortalHandle多模块网关。reflect+switch module的通用Retrieve。- 列表查询用
keyword字符串在仓储层switch module决定查哪一列(应落在各资源RetrieveXxxReq+ 各 repo 的查询闭包或AppendWhereFromStruct)。
交付验收(生成后自检)
- 每个对外资源具备独立
repository接口文件、service文件、handle文件。 - model:所有落库字段的
gormtag 均含comment:中文简短说明(含仅类型映射字段;gorm:"-"除外;与owl/docs/07-minimal-subapp-template.md示例一致)。 - label 标签:所有 Req 结构体中带
validate规则的字段均已加label:"中文名"标签(验证错误中文翻译必须)。 - 写操作 service 使用
redis.LockerFactory加锁(按资源+主键设计 key)。 - 领域错误使用
errContract.NewBizError,并在 service 包内集中定义错误码常量与构造函数(可按资源拆errors_xxx.go或分节组织)。 -
Binds()注册顺序:各NewXxxRepository→NewXxxService→NewXxxHandle。
业务错误约定
- 生成的 service 必须优先封装业务错误,不要把“已存在 / 不存在 / 状态不允许 / 重复操作 / 规则不满足”这类领域错误直接写成裸
errors.New(...)。 - 每个模块应在 service 包内定义:
- 业务错误码常量,如
CodePlanNotFound、CodeTaskNotSubmittable - 业务错误构造函数,如
PlanNotFound()、TaskNotSubmittable(msg string)
- 业务错误码常量,如
- 推荐使用
errContract.NewBizError(code, message)返回业务错误;让 handle 统一走router.Fail(...)。 - 只有真正的底层异常、第三方失败、未知系统错误才直接向上返回原始
error。 - 如果参考示例与本条冲突,以本节为准。
示例:
const (
CodePlanNotFound = "PLAN_NOT_FOUND"
CodeTaskLocked = "TASK_LOCKED"
)
func PlanNotFound() *errContract.BizError {
return errContract.NewBizError(CodePlanNotFound, "巡检计划不存在")
}
func TaskLocked() *errContract.BizError {
return errContract.NewBizError(CodeTaskLocked, "任务当前不可操作")
}
查询条件字段映射约定(db.AppendWhereFromStruct)
生成 RetrieveXxxReq 等列表查询 DTO 时:
- 操作符只写在 Go 字段名里(如
NameLike、CodeLike、EventAtBetween),json/formtag 不要照搬 Go 后缀。对外键名应表示业务列/含义,不带Like/Gte/Between等实现后缀。- 正例:
NameLike string \json:"name"`、CodeLike string `json:"code"`、CreatedAtBetween string `json:"createdAt"``。 - 反例:
NameLike string \json:"nameLike"``(易误导前端与 Bind 键名,且与「列名 + 操作符在 Go 侧解析」的约定不一致)。
- 正例:
- 时间范围:优先使用
EventAtBetween对应json:"eventAt"(或业务约定的单列名),值为逗号分隔两端点;不要默认拆成EventAtGte/EventAtLte和json:"eventAtGte"/json:"eventAtLte",除非产品明确要求拆开。 - 与精确条件同屏时避免 json 键冲突:若已有
Province对应json:"province"(等值),则ProvinceLike的 tag 不能再用"province",应使用可区分的键(如provinceFuzzy);AppendWhereFromStruct仍只读 Go 字段名,语义不变。
后端快速参考
- SubApp 契约:
Name、Bootstrap、ServiceProviders、Menu、Commands、RegisterRouters、Binds;结构体必须有app foundation.Application。 - Provider 选择:最小 HTTP+DB →
router.RouterServiceProvider、db.DBServiceProvider;加 RBAC → 再加permission.GuardProvider与 JWT Provider。需要文件上传 → 加storage.StorageServiceProvider。先查provider-reference.md,已有的直接用。 - 常见坑:漏注册 Binds、漏写 route、漏加 Migrate、缺
app字段、Swagger@Router与真实路径不一致、需要的能力框架已提供却自己重新实现。
重要约定
- 子应用均为独立包/独立项目,按
owl框架的 SubApp 方式搭建;入口用owl.NewApp(&xxx.SubAppXxx{}).WebShell()(不是.Run())。 - SubApp 与自定义 ServiceProvider 结构体必须包含字段
app foundation.Application,字段名必须是app。
后端自检清单
- 参考源码:生成代码前已用 Read 工具实际阅读了上方「参考文件」表中的 service / handle / repository 源码,而非凭记忆生成。
- 框架能力:所有功能需求已优先查过
provider-reference.md;凡框架已提供的,均通过 ServiceProvider 注入使用,未重复实现。 - Binds与路由:Binds 已注册、InitApi 已注册路由、InitMenu 已挂菜单。
- 数据库:Migrate 已加 model;model 全部落库字段已写
gorm列注释(comment:),无遗漏。 - 应用结构:SubApp 有
app字段。 - 验证:按
owl/docs/08-*做启动与接口验证。 - 黑盒测试:按
api-testing-guide.md执行并汇总结果。