Appearance
目录结构
IMPORTANT
我们极其重视目录结构的搭建,因为它定之前怎么定都行,而一旦定了,它会反过来管着你。所以项目的每一个目录创建,都经过深思熟虑。
服务端
Golang、Gin 并无推荐或标准的目录结构,我们结合了多个 AI 大模型的方案,反复确认了 go web 项目的通用目录结构,并要求它们提供参考来源,如 github 链接等,人工确认整理 top20 高星项目,最终得到了一份:完全贴合 Go 社区风格 + 企业级落地标准的目录结构设计。
设计随开发过程持续完善多次,主要特点如下:
- 和一些知名项目略有不同的是,目录名称全部使用单数(包括
config和script)- 首先是因为这是 Go 社区的命名惯例,比如
vendor、test,标准库也是清一色的单数命名 - 其次就是为了保持风格统一,特别是
internal下面的handler、model、service都是单数形式;很多项目外层是复数形式的configs,内层却是handler的单数形式。 - 以上命名方式都有自己的解释,但我认为最重要的还是 Go 社区给包命名的定义:包名描述的是"这个包是什么",而不是"这个包里装了什么",所以应该使用单数。
- 首先是因为这是 Go 社区的命名惯例,比如
- 没有
common目录,这是 Go 社区共识,因为它容易成为全局依赖洼地,极易诱发循环依赖,或者变成代码垃圾抽屉;转而使用internal/kit(成套工具箱)、internal/infra(基础设施),外层还有不依赖任何基础设施的pkg和pkg/util等粒度更细、更明确的包名代替。
bash
AI GO ADMIN
├─asset 一些私有静态资源文件
│ ├─captcha
│ │ └─click 点选验证码的图标、背景等
│ │
│ └─font 预设字体,目前用于点选验证码的中文渲染
│
├─cmd
│ │ main.go 程序主入口
│ │
│ ├─migrate 数据库迁移命令实现
│ │ │ commands.go
│ │ │ migrate.go
│ │ │ prefix.go
│ │ │
│ │ └─migrations 数据库迁移文件,为避免系统开始就有很多迁移,并没有一个数据表一个迁移文件,且和模型文件(/model)对应
│ │ 000001_admin.down.sql
│ │ 000001_admin.up.sql
│ │ 000002_common.down.sql
│ │ 000002_common.up.sql
│ │ 000003_seed.down.sql
│ │ 000003_seed.up.sql 种子数据
│ │
│ └─serve
│ serve.go API 服务入口
│
├─config 配置目录
│ captcha.yaml 验证码配置
│ config.yaml 未分类的综合配置
│ cors.yaml 跨域配置
│ upload.yaml 文件上传配置
│
├─internal
│ ├─dto 数据传输对象 DTO
│ │
│ ├─handler 控制器
│ │ │ base.go 基控制器实现
│ │ │
│ │ ├─admin 后台控制器
│ │ │
│ │ └─common 公共控制器
│ │
│ ├─infra 基础设施
│ │ ├─captcha
│ │ │ click.go 点选验证码
│ │ │
│ │ ├─config
│ │ │ config.go 配置加载,它导出了全局配置单例
│ │ │
│ │ ├─database
│ │ │ database.go 数据库连接,它导出了全局数据库连接句柄(但真正使用句柄时,一般从基仓储取)
│ │ │
│ │ ├─permission
│ │ │ admin.go 管理员鉴权实现
│ │ │
│ │ ├─token
│ │ │ │ token.go 身份令牌生成于验证等
│ │ │ │
│ │ │ └─driver 身份令牌多驱动支持
│ │ │
│ │ └─upload
│ │ │ upload.go 文件上传
│ │ │
│ │ └─driver 上传多驱动支持
│ │
│ ├─kit 成套辅助工具
│ │ ├─bindx
│ │ │ bindx.go 数据绑定
│ │ │
│ │ ├─httpx
│ │ │ context.go HTTP 上下文相关
│ │ │ response.go 创建 HTTP 响应
│ │ │
│ │ └─urlx
│ │ urlx.go URL 相关,目前主要是 FullURL(获取静态资源完整路径)
│ │
│ ├─middleware
│ │ admin_auth.go 管理员认证中间件
│ │ admin_log.go 管理员日志中间件
│ │ admin_permission.go 管理员验权中间件
│ │ cors.go 全局跨域中间件
│ │
│ ├─model 数据模型,和迁移一一对应;一个文件内可能含有多个模型是为了避免一开始就有很多文件
│ │ admin.go
│ │ common.go
│ │
│ ├─repository 仓储
│ │ │ base.go 基仓储实现
│ │ │
│ │ ├─admin 后台仓储
│ │ │
│ │ └─common 公共仓储
│ │
│ ├─router 路由注册,所有路由注册定义均在此目录内
│ │ │ index.go
│ │ │ router.go
│ │ │ static.go 静态资源路由
│ │ │
│ │ ├─admin 后台路由注册
│ │ │
│ │ ├─common 公共路由注册
│ │ │
│ │ └─registry 公共路由注册方法
│ │
│ └─service 服务
│ │ base.go 基服务
│ │
│ ├─admin 后台服务
│ │
│ └─common 公共服务
│
├─pkg 公共库代码(可被其他项目引用,以 x 结尾的它是表示对应包的扩展,比如 airx 就是 air 的扩展)
│ ├─airx
│ │
│ ├─copierx
│ │
│ ├─filesystem 文件系统相关
│ │
│ ├─jsonx
│ │
│ ├─random 随机数生成
│ │
│ ├─structx
│ │
│ ├─timex
│ │
│ ├─tree 树状数据生成
│ │
│ ├─util
│ │ ptr.go 指针工具
│ │ str.go 字符串工具
│ │
│ └─xss 反 XSS 工具
│
└─static
│ ├─images 静态文件
│ │
│ └─upload 默认上传目录
│ .air.toml Air 配置
│ .env.yaml.example 环境配置示例(改名为 .env.yaml 后使用,编译不嵌入二进制,同环境变量一样可覆盖同名配置项)
│ .gitignore GIT 忽略配置
│ AGENTS.md 智能体工作指导
│ embed.go 用于内嵌一些运行时资源
│ go.mod
│ go.sum
│ LICENSE
│ README.md前端
bash
WEB 使用经典目录结构设计,并合理优化
├─src
│ │ App.vue
│ │ main.ts
│ │
│ ├─api 存放所有接口请求函数的目录
│ │ │ common.ts
│ │ │ table.ts
│ │ │
│ │ └─admin 后台接口请求函数
│ │
│ ├─assets 静态资源
│ │
│ ├─components 组件
│ │ ├─agInput AIGO 输入组件
│ │ │ │ helper.ts
│ │ │ │ index.ts
│ │ │ │ index.vue 输入组件入口,可以向它传递 type 渲染任意输入组件,但仅建议在动态表单的场景使用
│ │ │ │
│ │ │ └─components
│ │ │ │ agUpload.vue 文件上传
│ │ │ │ areaSelect.vue 省份地区选择
│ │ │ │ array.vue 数组
│ │ │ │ editor.vue 富文本编辑器
│ │ │ │ iconSelect.vue 图标选择
│ │ │ │ remoteSelect.vue 远程下拉
│ │ │ │
│ │ │ └─editor 富文本编辑器目录,一个文件等于一个编辑器实现
│ │ │
│ │ ├─clickCaptcha 点选验证码
│ │ │
│ │ ├─contextmenu 右击菜单
│ │ │
│ │ ├─icon 图标
│ │ │
│ │ └─table 表格组件
│ │ │ index.ts
│ │ │ index.vue 表格入口组件
│ │ │ types.ts
│ │ │
│ │ ├─cellRenderer 表格单元格渲染器,一个文件等于一个渲染器,系统自动加载
│ │ │ buttons.vue
│ │ │ color.vue
│ │ │ customRender.vue
│ │ │ customTemplate.vue
│ │ │ datetime.vue
│ │ │ default.vue
│ │ │ icon.vue
│ │ │ image.vue
│ │ │ images.vue
│ │ │ switch.vue
│ │ │ tag.vue
│ │ │ tags.vue
│ │ │ url.vue
│ │ │
│ │ └─header 表格表头组件
│ │ comSearch.vue
│ │ index.vue
│ │
│ ├─hooks
│ │ useDark.ts 暗黑模式相关
│ │ useGlobalProperties.ts 全局 vue 实例相关
│ │ useTableManager.ts 表格管家
│ │
│ ├─lang 多语言
│ │ │ index.ts
│ │ │
│ │ ├─en 英文语言包
│ │ │
│ │ └─zh-cn 中文语言包
│ │
│ ├─layouts 布局
│ │ ├─admin 后台布局
│ │ │
│ │ └─common 公共布局
│ │
│ ├─router
│ │ │ index.ts
│ │ │ static.ts 静态路由加载
│ │ │
│ │ └─static 静态路由目录,一个文件一组静态路由,可自动发现和加载
│ │ adminBase.ts 后台静态路由
│ │
│ ├─stores 状态商店
│ │ │ adminInfo.ts 管理员信息
│ │ │ config.ts 配置数据(布局配置、多语言、站点配置)
│ │ │ index.ts
│ │ │ menu.ts 后台菜单数据
│ │ │ navTab.ts 后台导航栏数据
│ │ │ ref.ts 全局引用句柄
│ │ │
│ │ ├─constant 全局常量定义
│ │ │ cacheKey.ts
│ │ │ common.ts
│ │ │
│ │ └─interface 状态商店接口定义
│ │ config.ts
│ │ index.ts
│ │
│ ├─styles 样式
│ │ app.scss 全局样式
│ │ dark.scss 黑暗模式样式
│ │ element.scss Element Plus 的样式覆盖
│ │ index.scss 统一入口,main.ts 只导入它
│ │ loading.scss 全局 Loading 样式
│ │ mixins.scss mixins 与 function 定义
│ │ var.scss scss 变量定义
│ │
│ ├─utils 工具
│ │ common.ts 公共工具(前端不存在依赖循环,可以用 common)
│ │ dev.ts 开发环境辅助
│ │ horizontalScroll.ts 横向滚动条
│ │ layout.ts 布局相关工具
│ │ loading.ts 全局 loading
│ │ mask.ts 全局 mask
│ │ random.ts 随机数生成
│ │ request.ts 请求封装(Axios)
│ │ router.ts 路由相关,如动态路由注册
│ │ storage.ts 浏览器存储(Local 和 Session)
│ │ validate.ts 表单数据验证
│ │ vite.ts Vite 相关工具
│ │
│ └─views 视图(vue 页面组件)
│ │ index.vue 首页
│ │
│ ├─admin 后台页面
│ │
│ └─common 公共页面
│
└─types 全局类型定义
│ .editorconfig IDE 风格统一配置
│ .env 基础环境变量定义
│ .env.development 开发环境变量定义
│ .env.production 生产环境变量定义
│ .prettierignore prettier 忽略
│ .prettierrc.js prettier 配置
│ eslint.config.js eslint 配置
│ index.html 入口文件
│ package.json
│ pnpm-lock.yaml
│ tsconfig.json ts 配置
│ vite.config.ts vite 配置顺便一提,前端的命名规范很有意思,因为每个厂都有自己的规范。比如:
- 目录名使用
kebab-case,但组件的目录名又随组件的文件名使用PascalCase; hooks文件名使用kebab-case,但tools文件名应该使用camelCase;- 全局类型定义使用
types/kebab-case.ts,但单个组件类型定义文件名随组件名使用PascalCase.types.ts
等等花里胡哨的设计,如果你要统计 top20 的高星仓库,其结果可能会让你头大如斗,这太乱了,稍微不注意就会用错。
- 那么目录名大小写乱用,有影响吗?没影响
- 组件小写开头,影响导入、影响编译吗?不影响
- 全局类型定义文件名使用
camelCase有影响吗?也没有影响
过度区分命名格式会显著增加团队协作成本、增加出错概率,换为以下规范:
| 对象 | 命名格式 |
|---|---|
| Interface / Type 名 | PascalCase |
| class | PascalCase |
| 其余(组件可选的导入为 PascalCase) | camelCase |
在 vue 中,只要你能写对导入路径,那么代码就能正常运行,以上这版 简单粗暴 的命名方式我已经使用几年了,没有出现过任何因为命名产生的问题,实际证明统一使用 camelCase 既不影响功能实现,又能显著降低心智负担,看着也非常舒服。
另外,我知道 vue 官方文档写了组件名推荐 PascalCase 或者 kebab-case,不过这是为了保持和 html 的标签一样的命名方式,很多人还会将 kebab-case 的组件文件名,全局注册为 PascalCase 组件,反而导致一些问题。
