1.3 项目目录结构
概述
在软件开发过程中,优秀的目录结构对项目的组织、开发、维护和扩展至关重要。本项目后端采用"单包平铺"结构(api/controller/logic/service 各一个包,每模块一个文件),与常见的"按域分目录"不同——这是刻意的设计决策,详见下方说明。
设计原则
text
1. 目录平铺:api/v1、controller、logic、service 均为单包,每模块一个 snake_case 文件;
跨模块直接同包调用(如 user 直接调用 ResolveCityNames,无需 import)。
2. 分层架构:controller(HTTP)→ service 接口(依赖倒置)→ logic(业务)→ dao(数据),职责清晰。
3. 模块化管理:25 张表对应 27 个模块文件,文件名 = 表名去 goframe_ 前缀。
4. 依赖红线:中间件不依赖业务层(函数变量接线)、task 单向依赖 logic、migrate 不走 dao。项目根目录
text
├── main.go # 服务入口:注册五库驱动 + Redis 适配器 → cmd.Run
├── Makefile # run/build/test/vet/fmt/gen(gen-dao)/clean
├── api/ # 全部请求/响应结构体(单包 v1)
│ └── v1/ # 每模块一文件:user.go、config_item.go…
├── cmd/ # CLI 入口
│ ├── generator/ # 代码生成器 CLI(go run ./cmd/generator)
│ └── migrator/ # 整库迁移 CLI(go run ./cmd/migrator)
├── internal/ # 后端核心代码(详见下方)
├── templates/ # 代码生成器模板(后端 {{ }} 定界;ui* 前端 [[ ]] 定界)
│ └── ui/ ui2/ # 前端模板(ui2 为树形模块变体)
├── utility/ # 基础能力包(无业务依赖)
├── manifest/
│ ├── config/config.yaml # 运行时配置(唯一配置文件)
│ └── data/ip2region.xdb # IP 归属地库
├── resource/
│ ├── static/ # 静态资源(验证码字体等)
│ ├── swagger/ # 本地托管 API 文档页面
│ └── upload/ # 上传文件静态服务根(SetServerRoot "resource")
├── hack/ # gf gen dao 配置与自定义模板、黄金样本回归基线
├── .dockerignore # Docker 构建上下文排除清单(硬规则:必须在上下文根目录)
├── docker/ # Docker 化文件(构建上下文为项目根,-f docker/Dockerfile)
│ ├── Dockerfile # 多阶段构建:静态编译 → alpine 运行(非 root + HEALTHCHECK)
│ ├── docker-compose.yml # 三服务编排:后端 + MySQL8 + Redis7(GF_ 注入配置)
│ └── .env.example # compose 变量模板(复制为 .env,明文密钥不入库)
├── ui/ # 前端工程(Vue3 + Element Plus)
├── document/ # 项目文档(五方言 SQL 脚本、架构定稿、wiki 等)
│ ├── mysql/ # MySQL 定稿 DDL(规范基准)
│ ├── postgresql/ # PostgreSQL 定稿 DDL
│ ├── sqlserver/ # SQL Server 定稿 DDL
│ ├── oracle/ # Oracle 定稿 DDL
│ ├── sqlite/ # SQLite 定稿 DDL
│ ├── architecture.md # 架构定稿
│ └── wiki/ # 后端开发者文档
├── upload/ # 上传物理目录(file.uploadDir)
├── log/ # 运行日志
└── bin/ # 编译产物后端目录(internal/)
text
internal/
├── cmd/ # 启动编排:cache.Init → 依赖接线 → 路由 → 调度器 → 运行
├── consts/ # 业务错误码 / 缓存键 / 上下文键 / 日志类型枚举
│ ├── biz_err.go # 17 个业务错误码(gcode.Register 语义值)
│ ├── cache_key.go # Redis 键统一前缀 easygoadmin:
│ ├── context_key.go # 上下文读写(consts.GetXxx(ctx))
│ └── log_type.go # 41 个操作日志类型常量与静态映射
├── controller/ # 控制器(单包,薄层,仅转发 service)
│ ├── user.go # CUser:Page/List/Detail/Add/Update/Status/…
│ └── … # 文件命名 snake_case 对齐 dao
├── dao/ # 数据访问层(自定义模板产出,挂钩子 + Ctx 覆写)
│ └── internal/ # dao 内部实现(自动生成,勿手改)
├── logic/ # 业务逻辑(单包,每模块一文件,同包 init 注册)
│ ├── base.go # 共享能力层(文件/富文本/字典/事务/分块/树,15 个导出上限)
│ ├── module_spec.go # ModuleSpec 泛型声明式 CRUD 基座
│ ├── data_scope.go # 行级数据权限共享工厂 ApplyDataScope
│ ├── user.go role.go … # 各模块业务实现(sUser/sRole…)
│ └── generator_side.go # 生成器副作用(菜单/字典/路由注册)
├── middleware/ # 7 个中间件 + 白名单 + 依赖接线(wire.go)
│ ├── cors.go response.go # CORS / 响应兜底(SVG 加固、panic 恢复)
│ ├── ratelimit.go demo.go # 限流 / 演示模式
│ ├── jwt.go tenant.go # 认证 / 租户状态校验
│ ├── operation_log.go # 操作日志异步采集
│ ├── public.go # 免登录白名单(精确匹配)
│ └── wire.go # IdentityLoader/TenantLoader/LogSink 函数变量
├── migrate/ # 整库迁移引擎(内省/翻译/DDL 渲染/搬运/序列修复)
├── model/
│ ├── entity/ # 实体(gf gen dao 产物)
│ ├── do/ # 数据对象 DO(gf gen dao 产物)
│ └── hooks/ # 全局模型钩子(审计盖章/软删改写/租户隔离)
├── router/ # 路由注册(router.go)+ gmeta 反射绑定(bind.go)
│ └── meta/ # 路由元数据表(权限/日志注解即事实)
├── service/ # 服务接口层(每模块一文件,手维护,勿运行 gf gen service)
└── task/ # 定时任务处理器(init 自注册到 logic.RegisterJobHandler)
├── task.go # 注册与参数辅助
├── hello_world.go # 示例任务(验证调度链路)
├── send_sms.go # 短信示例任务
└── log_clean.go # 日志清理内置任务(每日 02:30)前端目录(ui/src/)
text
ui/src/
├── api/ # 接口模块(按域分目录:system/data/content/monitor/tool…)
├── components/ # 公共组件(25 类:Table/Form/Upload/Editor/Excel/ChinaArea…)
├── directives/ # 自定义指令(permission 权限指令等)
├── enums/ # 枚举常量(http/cache/page/permissions/role…)
├── hooks/ # 组合式函数(core/event/setting/web…)
├── layout/ # 布局系统
├── router/ # 路由(constant 常量路由 + generator-routers 动态菜单路由)
├── settings/ # 全局设置(项目设置/设计设置/组件设置/动画设置)
├── store/ # Pinia(modules:user/asyncRoute/tabsView/designSetting…)
├── styles/ # 全局样式与主题变量
├── utils/ # 工具库(http 请求封装/auth/storage/downloadFile…)
└── views/ # 页面(按域分目录)
├── system/ # user/role/menu/dept/level/position/tenant/logger
├── data/ # city/config/dict/notice/param
├── content/ # article/category/link
├── monitor/ # online/server/cache/job
├── tool/ # generator/example
├── file/ setting/ dashboard/ login/ exception/ iframe/ redirect/ about/为什么选择"单包平铺"而非"按域分目录"
| 对比项 | 单包平铺(本项目) | 按域分目录 |
|---|---|---|
| 跨模块复用 | 同包直接调用包级函数,零 import | 需要 import,易产生循环依赖 |
| 新增模块 | 4 个文件各放一处,位置固定无歧义 | 需要在多级目录间跳转 |
| 生成器产出 | 目标路径唯一确定 | 需推断目录层级 |
| 包体积 | 单包文件多,但 Go 编译无压力 | 包粒度细 |
| init 注册 | 同包全量加载,无需 blank import | 需要 blank import 触发 |
文件命名统一为表名去 goframe_ 前缀的 snake_case(如 config_item.go、file_template.go),与 dao 产物命名对齐;前端文件仍使用驼峰(如 fileTemplate.ts)。