Freedom 框架集成了 Iris Web 框架的路由功能,并在此基础上提供了更符合 DDD 架构风格的路由注册和处理方式。本指南将介绍如何在 Freedom 项目中配置和使用路由功能。
// adapter/controller/user_controller.go
package controller
import (
"github.com/8treenet/freedom"
"github.com/kataras/iris/v12"
)
func init() {
// 注册 Controller
freedom.Prepare(func(initiator freedom.Initiator) {
initiator.BindController("/api/v1", &UserController{})
})
}
// UserController 用户控制器
type UserController struct {
Worker freedom.Worker
// 依赖注入的 Service
UserService *service.UserService
}
// Get 处理 GET 请求
// 路由: GET /api/v1/users/:id
func (c *UserController) Get(ctx iris.Context) {
userID, _ := ctx.Params().GetInt("id")
user, err := c.UserService.GetUserByID(userID)
if err != nil {
ctx.StatusCode(iris.StatusNotFound)
return
}
ctx.JSON(user)
}
// Post 处理 POST 请求
// 路由: POST /api/v1/users
func (c *UserController) Post(ctx iris.Context) {
var req struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := ctx.ReadJSON(&req); err != nil {
ctx.StatusCode(iris.StatusBadRequest)
return
}
err := c.UserService.CreateUser(req.Name, req.Email)
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
return
}
ctx.StatusCode(iris.StatusCreated)
}
Freedom 框架支持以下路由注册方式:
// 1. RESTful 风格路由(自动根据方法名映射)
type UserController struct {
// ...
}
func (c *UserController) Get() {} // GET /api/v1/users
func (c *UserController) Post() {} // POST /api/v1/users
func (c *UserController) Put() {} // PUT /api/v1/users
func (c *UserController) Delete() {} // DELETE /api/v1/users
// 2. 自定义方法路由
func (c *UserController) GetProfile() {} // GET /api/v1/users/profile
func (c *UserController) PostAvatar() {} // POST /api/v1/users/avatar
func (c *UserController) PutPassword() {} // PUT /api/v1/users/password
func (c *UserController) DeleteCache() {} // DELETE /api/v1/users/cache
// 3. 带参数的路由
func (c *UserController) GetBy(id int) {} // GET /api/v1/users/{id}
func (c *UserController) GetProfileBy(id int) {} // GET /api/v1/users/{id}/profile
// middleware/auth.go
package middleware
import (
"github.com/kataras/iris/v12"
)
func AuthMiddleware(ctx iris.Context) {
token := ctx.GetHeader("Authorization")
if token == "" {
ctx.StatusCode(iris.StatusUnauthorized)
return
}
// 验证 token ...
ctx.Next()
}
// 在 controller 中使用中间件
func init() {
freedom.Prepare(func(initiator freedom.Initiator) {
// 全局中间件
initiator.BindMiddleware(AuthMiddleware)
// 特定路由组中间件
initiator.BindController("/api/v1", &UserController{}, AuthMiddleware)
})
}
// 1. 路径参数
func (c *UserController) GetBy(id int) {
// id 参数会自动解析并注入
user, err := c.UserService.GetUserByID(id)
// ...
}
// 2. 查询参数
func (c *UserController) Get(ctx iris.Context) {
page := ctx.URLParamIntDefault("page", 1)
size := ctx.URLParamIntDefault("size", 10)
// ...
}
// 3. JSON 请求体
func (c *UserController) Post(ctx iris.Context) {
var req struct {
Name string `json:"name"`
Email string `json:"email"`
}
if err := ctx.ReadJSON(&req); err != nil {
ctx.StatusCode(iris.StatusBadRequest)
return
}
// ...
}
// 4. 表单数据
func (c *UserController) PostForm(ctx iris.Context) {
name := ctx.FormValue("name")
file, _, err := ctx.FormFile("avatar")
if err != nil {
ctx.StatusCode(iris.StatusBadRequest)
return
}
// ...
}
// 1. JSON 响应
func (c *UserController) Get(ctx iris.Context) {
user, err := c.UserService.GetUser()
if err != nil {
ctx.JSON(iris.Map{
"error": err.Error(),
})
return
}
ctx.JSON(user)
}
// 2. 自定义状态码
func (c *UserController) Post(ctx iris.Context) {
err := c.UserService.CreateUser()
if err != nil {
ctx.StatusCode(iris.StatusBadRequest)
ctx.JSON(iris.Map{
"error": err.Error(),
})
return
}
ctx.StatusCode(iris.StatusCreated)
}
// 3. 文件下载
func (c *UserController) GetExport(ctx iris.Context) {
data := c.UserService.ExportData()
ctx.Header("Content-Disposition", "attachment; filename=export.xlsx")
ctx.Binary(data)
}
// 按业务领域组织路由
initiator.BindController("/api/v1/users", &UserController{})
initiator.BindController("/api/v1/orders", &OrderController{})
initiator.BindController("/api/v1/products", &ProductController{})
// 版本控制
initiator.BindController("/api/v1", &V1Controller{})
initiator.BindController("/api/v2", &V2Controller{})
// common/errors.go
var (
ErrNotFound = errors.New("resource not found")
ErrInvalidInput = errors.New("invalid input")
)
// controller/base.go
type BaseController struct {
Worker freedom.Worker
}
func (c *BaseController) HandleError(ctx iris.Context, err error) {
switch err {
case ErrNotFound:
ctx.StatusCode(iris.StatusNotFound)
case ErrInvalidInput:
ctx.StatusCode(iris.StatusBadRequest)
default:
ctx.StatusCode(iris.StatusInternalServerError)
}
ctx.JSON(iris.Map{
"error": err.Error(),
})
}
// 使用结构体标签进行验证
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=2,max=50"`
Email string `json:"email" validate:"required,email"`
Age int `json:"age" validate:"required,gte=0,lte=150"`
}
func (c *UserController) Post(ctx iris.Context) {
var req CreateUserRequest
if err := ctx.ReadJSON(&req); err != nil {
ctx.StatusCode(iris.StatusBadRequest)
return
}
if err := c.Worker.Validator().Struct(req); err != nil {
ctx.StatusCode(iris.StatusBadRequest)
ctx.JSON(iris.Map{
"error": err.Error(),
})
return
}
// ...
}
func init() {
freedom.Prepare(func(initiator freedom.Initiator) {
app := initiator.Iris()
// 自定义路由组
api := app.Party("/api")
{
api.Use(AuthMiddleware)
api.Get("/health", HealthCheck)
api.Post("/webhook", WebhookHandler)
}
})
}
func (c *ChatController) GetWs(ctx iris.Context) {
ws := websocket.New(websocket.Config{})
ws.OnConnection(func(c websocket.Connection) {
// 处理 WebSocket 连接
c.OnMessage(func(data []byte) {
// 处理消息
})
c.OnDisconnect(func() {
// 处理断开连接
})
})
ws.Upgrade(ctx)
}
// server/main.go
func main() {
app := freedom.NewApplication()
// Kafka 配置
kafkaConf := sarama.NewConfig()
kafkaConf.Version = sarama.V0_11_0_2
// 启动消费者
kafka.GetConsumer().Start(
[]string{":9092"}, // Kafka 服务器地址
"freedom1", // 消费者组
kafkaConf, // Kafka 配置
"http://127.0.0.1:8000", // 回调地址,当前服务端口号,建议runner使用app.NewH2CRunner
true, // 是否为H2C代理
)
// ... 其他配置
app.Run(addrRunner, conf.Get().App)
}
// adapter/controller/goods_controller.go
package controller
import (
"github.com/8treenet/freedom"
"github.com/8treenet/freedom/example/infra-example/domain/event"
)
func init() {
// 注册 Controller
freedom.Prepare(func(initiator freedom.Initiator) {
// 绑定控制器
initiator.BindController("/api/v1", &GoodsController{})
// 监听 Kafka 事件
// 参数1: Topic名称
// 参数2: 处理方法名 "控制器名称.方法名"
initiator.ListenEvent((&event.ShopGoods{}).Topic(), "GoodsController.PostShop")
})
}
// GoodsController 商品控制器
type GoodsController struct {
Worker freedom.Worker //依赖倒置
GoodsService *service.GoodsService //依赖注入
}
// PostShop 处理商店商品事件
// 注意:方法名必须以 HTTP 方法开头(Post/Get/Put/Delete)
func (c *GoodsController) PostShop(shopGoods *event.ShopGoods) error {
// 处理接收到的商品事件
c.Worker.Logger().Infof("接收到商品事件: %+v", shopGoods)
// 调用服务处理业务逻辑
return c.GoodsService.HandleShopGoods(shopGoods)
}
// domain/event/shop_goods.go
package event
// ShopGoods 商店商品事件
type ShopGoods struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
ShopID int `json:"shop_id"`
}
// Topic 返回事件主题
func (s *ShopGoods) Topic() string {
return "shop.goods"
}
- Controller 方法命名要符合规范,以 HTTP 方法开头
- 路由参数类型要匹配,否则会导致路由注册失败
- 合理使用中间件,避免性能问题
- 请求参数验证要完整
- 统一错误处理方式
- 合理组织路由结构,避免路由冲突