目录结构应按业务域切分,如users/、orders/各含controller、service、repository、model,而非models/controllers/views平行目录;公共能力放pkg/或internal/,避免跨域引用混乱。

目录结构必须按业务域切分,不能按技术层硬套
很多人一上来就照搬“MVC三层”建 models、controllers、views 三个平行目录,结果不到两周就发现:用户模块的 service 要调订单模型,订单 controller 又要复用用户校验逻辑——所有跨域引用都得靠相对路径硬导,../../models/user.go 写到第三遍就开始怀疑人生。
正确做法是先划业务边界,每个业务域自成闭环:
-
users/目录下放controller.go、service.go、repository.go、model.go -
orders/同理,不共享models/全局目录 - 公共基础能力(如日志、错误码、JWT 工具)才放进
pkg/或internal/
这样改用户密码逻辑时,只打开 users/ 就够了,不会被订单的 SQL 日志刷屏干扰。
controller 文件里别塞业务逻辑,只做参数解析和响应组装
BookController 的 GetByID 方法里写数据库查询或字段校验,是 MVC 最常见的污染点。Iris 的 controller 层本质是“胶水”,职责就是把请求转成 service 调用,再把结果包成 mvc.Response。
典型错误写法:
func (c *BookController) GetByID(id int64) mvc.Result {
// ❌ 在 controller 里直接查 DB
db := getDB()
var book Book
db.Where("id = ?", id).First(&book)
if book.ID == 0 {
return mvc.Response{StatusCode: 404}
}
return mvc.Response{ContentType: "application/json", Text: toJSON(book)}
}
应该拆成:
-
users/service.go提供GetUserByID(ctx context.Context, id int64) (*User, error) -
users/controller.go只负责:user, err := c.userService.GetUserByID(c.Ctx, id)+ 错误转 HTTP 状态码
否则 service 层永远抽不出来,后续加缓存、换数据库、写单元测试全卡死。
BeforeActivation 是依赖注入的唯一入口,别在构造函数里 new 实例
看到 new(BookController) 就以为能直接传参初始化?Iris 不支持构造函数注入。所有依赖(UserService、Logger、Cache)必须通过 BeforeActivation 注册,否则每次请求都是新实例,连接池、单例缓存全失效。
正确姿势:
func (c *BookController) BeforeActivation(b mvc.BeforeActivation) {
// ✅ 注入单例 service
b.Dependencies().Add(func() *UserService {
return userServiceInstance // 全局变量 or 从 container.Get()
})
// ✅ 绑定带参数的路径
b.Handle("GET", "/{id:long}", "GetByID")
}
容易踩的坑:
- 在
BeforeActivation外部用c.userService = &UserService{}—— 每次请求新建,DB 连接爆炸 - 注册依赖顺序错乱:比如
Cache依赖RedisClient,但RedisClient没先注册 —— 启动直接 panic
静态资源目录和模板路径必须用绝对路径,别信 ./
app.RegisterView(iris.HTML("./web/views", ".html")) 在开发机上跑得欢,打包成二进制扔到服务器就 404——因为 ./web/views 解析的是进程当前工作目录,不是项目根目录。
可靠写法:
import (
"os"
"path/filepath"
)
exePath, _ := os.Executable()
rootDir := filepath.Dir(exePath)
viewsDir := filepath.Join(rootDir, "web", "views")
app.RegisterView(iris.HTML(viewsDir, ".html"))
staticDir := filepath.Join(rootDir, "public")
app.Static("/static", staticDir)
关键点:
-
os.Executable()拿到的是二进制文件位置,比os.Getwd()可控得多 - 模板路径和静态路径的 URL 前缀(如
/static)必须和 HTML 中的<link href="/static/main.css">完全一致 - Nginx 反代时,
alias配置末尾必须带斜杠:alias /opt/myapp/public/;,少一个就拼错路径
复杂点在于:业务域目录(如 users/)和公共资源(public/)物理位置可能不在同一级,但 URL 路径必须对齐。这点不提前想清楚,上线后调试静态资源 404 能耗掉半天。


















