Skip to content

4.5 目录结构设计 ​

概述

目录结构是架构决策的物理体现。本节解释"为什么这样放"——平铺单包、snake_case 命名、internal 边界、utility 无业务依赖等约定背后的原因。完整目录树见 1.3 项目目录结构。

后端顶层布局与设计意图 ​

text
main.go          入口极薄:注册五库驱动 + Redis 适配器 → cmd.Run
internal/        私有包边界(Go internal 语义:外部不可 import)
  cmd/           启动编排独立成包——把 main 的"做事"与"声明"分离
  api/v1/        契约层放 internal 外?——不,本项目放顶层 api/,
                 便于生成器与 gf 工具链定位,也表达"对外契约"语义
  controller/ logic/ service/ dao/   四层各自单包平铺
  middleware/    中间件 + wire.go 函数变量接线(不 import 业务包)
  migrate/       迁移引擎独立——gdb 裸连,必须与 dao 体系隔离
  model/hooks/   全局钩子独立于 entity/do(生成物与手写物分离)
  router/        router.go(注册)+ bind.go(反射)+ meta/(元数据表)
  task/          定时任务实现——单向依赖 logic,cmd 空白导入触发
templates/       生成器模板独立于 internal(模板演进与代码解耦)
utility/         基础能力包——无业务依赖,可供任何层使用
manifest/        配置与数据(ip2region.xdb)
resource/        静态资源(SetServerRoot 提供 /upload/**、swagger 页面)
hack/            工具链配置(gf gen dao 模板、golden 回归基线)

平铺 vs 分目录的取舍 ​

维度单包平铺(选择)按域分目录(弃用)
跨模块复用同包直接调用包级函数,零 import、无循环依赖风险需 import,深组合易循环
新增模块4 个文件各就各位,无目录推断多级目录间跳转
生成器目标路径唯一确定需推断层级
init 注册同包全量加载,无需 blank import每包需 blank import
代价单包文件多(30+)包粒度细

Go 编译器对大包无压力,而"跨模块直接调用"的收益在中后台系统里极高(city 编解码、身份加载、字典文本被大量模块复用)。

文件命名规范 ​

text
后端(对齐 dao 产物):表名去 goframe_ 前缀的 snake_case
  api/v1/config_item.go · internal/controller/config_item.go
  internal/logic/config_item.go · internal/service/config_item.go

前端:驼峰
  src/api/data/config.ts · src/views/data/configItem/

原因:后端文件名与 dao/entity/do 生成物一致,生成器与人工开发心智统一;
      前端沿用 JS 社区驼峰惯例,两边各自从俗。

internal 的边界价值 ​

internal/ 是 Go 编译器强制的私有边界——仓库外代码无法 import,防止外部项目把实现细节当公共 API 依赖。分层红线(controller 不 import dao 等)靠 code review + 生成器模板保证,internal 防的是"仓库外扩散"。

utility 的纪律 ​

text
utility/* 只依赖标准库与三方库,禁止 import internal(防反向耦合)

cache      统一缓存门面(Redis 优先、内存降级)
captcha    验证码生成/校验
crypto     bcrypt + salt
excel      excelize 封装(ReadSheet/WriteSheet)
ip2region  xdb 归属地(懒加载 + 读锁)
jwt        HS256 双令牌 Sign/Parse
response   统一信封写出
ua         OS/Browser 解析
upload     两段式上传(SaveTemp/SaveFromTemp/GetFileUrl)

前端目录设计 ​

text
views 按"域"分目录(system/data/content/monitor/tool…)与后端模块域对应;
api 同样按域分目录;
components/store/utils/hooks 不按域——全局共享层;
settings/styles/enums 全局配置面。

views 域归属与生成器 genDomainMap 一致(user/role→system,config→data,article→content,operation_log→monitor,example→tool,默认 tool),生成器据此决定前端产出路径。

小蚂蚁云团队 · 提供技术支持