网络应用
功能特性
- Web MVC(模型-视图-控制器)
- 基于属性配置的自动配置和依赖注入
- 支持
inject:""标签或构造函数注入 - 基于方法名的自动路由映射
- 内置验证支持
- Swagger/OpenAPI 文档支持
Hiboot MVC 简介
Hiboot 旨在隐藏与业务无关的代码,让开发者专注于业务逻辑。
与大多数 Go Web 框架不同,Hiboot 无需手动配置路由。Hiboot 使用反射根据控制器方法名自动构建路由。
项目结构
.
├── config
│ ├── application.yml
│ ├── application-local.yml
│ └── application-dev.yml
├── main.go
├── main_test.go
├── controller
│ ├── user.go
│ └── user_test.go
├── entity
│ └── user.go
└── service
├── user.go
└── user_test.go
应用配置
Hiboot 允许你将配置外部化,以便在不同环境中使用相同的应用程序代码。
属性值可以使用 value:"" 标签直接注入到结构体中:
type MyService struct {
AppName string `value:"${app.name}"`
Port int `value:"${server.port}"`
}
config/application.yml
app:
project: myproject
name: myapp
profiles:
include:
- actuator
- logging
server:
port: 8080
logging:
level: info
应用配置属性参考
| 字段 | 描述 | 示例 |
|---|---|---|
app.project |
项目名称 | myproject |
app.name |
应用名称 | myapp |
app.profiles.active |
激活的配置文件 | dev, test, prod |
app.profiles.include |
包含的 Starter | actuator, logging, swagger |
server.port |
服务器端口 | 8080 |
logging.level |
日志级别 | debug, info, warn, error |
特定环境配置
可以使用命名约定 application-${profile}.yml 定义特定环境的配置。
config/application-local.yml
server:
port: 8081
logging:
level: debug
特定环境的配置文件会覆盖基础 application.yml 中的属性。
编写代码
main.go
main 包是 Web 应用程序的入口点:
package main
import (
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/app/web"
"github.com/hidevopsio/hiboot/pkg/starter/actuator"
"github.com/hidevopsio/hiboot/pkg/starter/logging"
_ "myapp/controller"
)
func main() {
web.NewApplication().
SetProperty(app.ProfilesInclude, actuator.Profile, logging.Profile).
Run()
}
控制器 - controller/user.go
控制器处理 HTTP 请求。嵌入 at.RestController 将结构体标记为 REST 控制器:
package controller
import (
"myapp/entity"
"myapp/service"
"net/http"
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/at"
"github.com/hidevopsio/hiboot/pkg/model"
)
// userController 处理用户相关的 HTTP 请求
type userController struct {
at.RestController
at.RequestMapping `value:"/user"`
userService service.UserService
}
func init() {
app.Register(newUserController)
}
// newUserController 通过构造函数注入 userService
func newUserController(userService service.UserService) *userController {
return &userController{
userService: userService,
}
}
// Post 处理 POST /user
func (c *userController) Post(request *entity.User) (model.Response, error) {
err := c.userService.AddUser(request)
response := new(model.BaseResponse)
response.SetData(request)
return response, err
}
// GetById 处理 GET /user/id/{id}
func (c *userController) GetById(id uint64) (response model.Response, err error) {
user, err := c.userService.GetUser(id)
response = new(model.BaseResponse)
if err != nil {
response.SetCode(http.StatusNotFound)
} else {
response.SetData(user)
}
return
}
// GetAll 处理 GET /user/all
func (c *userController) GetAll() (response model.Response, err error) {
users, err := c.userService.GetAll()
response = new(model.BaseResponse)
response.SetData(users)
return
}
// DeleteById 处理 DELETE /user/id/{id}
func (c *userController) DeleteById(id uint64) (response model.Response, err error) {
err = c.userService.DeleteUser(id)
response = new(model.BaseResponse)
return
}
HTTP 方法映射
方法名会自动映射到 HTTP 方法:
| 方法名 | HTTP 方法 | 路由示例 |
|---|---|---|
Get |
GET | GET /user |
GetById |
GET | GET /user/id/{id} |
GetAll |
GET | GET /user/all |
Post |
POST | POST /user |
Put |
PUT | PUT /user |
PutById |
PUT | PUT /user/id/{id} |
Delete |
DELETE | DELETE /user |
DeleteById |
DELETE | DELETE /user/id/{id} |
实体 - entity/user.go
实体代表业务模型:
package entity
import "github.com/hidevopsio/hiboot/pkg/model"
type User struct {
model.RequestBody
Id uint64 `json:"id"`
Name string `json:"name" validate:"required"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Email string `json:"email" validate:"required,email"`
Age uint `json:"age" validate:"gte=0,lte=130"`
Gender uint `json:"gender" validate:"gte=0,lte=2"`
}
func (u *User) TableName() string {
return "user"
}
服务 - service/user.go
服务实现业务逻辑:
package service
import (
"errors"
"myapp/entity"
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/utils/idgen"
)
type UserService interface {
AddUser(user *entity.User) error
GetUser(id uint64) (*entity.User, error)
GetAll() (*[]entity.User, error)
DeleteUser(id uint64) error
}
type userServiceImpl struct {
UserService
users map[uint64]*entity.User
}
func init() {
app.Register(newUserService)
}
func newUserService() UserService {
return &userServiceImpl{
users: make(map[uint64]*entity.User),
}
}
func (s *userServiceImpl) AddUser(user *entity.User) error {
if user == nil {
return errors.New("user 不能为空")
}
if user.Id == 0 {
user.Id, _ = idgen.Next()
}
s.users[user.Id] = user
return nil
}
func (s *userServiceImpl) GetUser(id uint64) (*entity.User, error) {
user, ok := s.users[id]
if !ok {
return nil, errors.New("用户不存在")
}
return user, nil
}
func (s *userServiceImpl) GetAll() (*[]entity.User, error) {
users := make([]entity.User, 0, len(s.users))
for _, u := range s.users {
users = append(users, *u)
}
return &users, nil
}
func (s *userServiceImpl) DeleteUser(id uint64) error {
delete(s.users, id)
return nil
}
使用注解生成 Swagger 文档
使用控制器方法中的注解生成 Swagger/OpenAPI 文档:
package controller
import (
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/at"
)
type userController struct {
at.RestController
at.RequestMapping `value:"/user"`
}
func init() {
app.Register(newUserController)
}
func newUserController() *userController {
return &userController{}
}
// GetById 处理 GET /user/{id},包含 Swagger 文档
func (c *userController) GetById(
at struct {
at.GetMapping `value:"/{id}"`
at.Operation `id:"getUserById" description:"根据 ID 获取用户"`
at.Produces `values:"application/json"`
Parameters struct {
Id struct {
at.Parameter `name:"id" in:"path" type:"integer" description:"用户 ID"`
}
}
Responses struct {
StatusOK struct {
at.Response `code:"200" description:"成功"`
}
NotFound struct {
at.Response `code:"404" description:"用户不存在"`
}
}
},
id uint64,
) string {
return "用户详情"
}
运行应用
go run main.go
输出:
______ ____________ _____
___ / / /__(_)__ /_______________ /_
__ /_/ /__ /__ __ \ __ \ __ \ __/
_ __ / _ / _ /_/ / /_/ / /_/ / /_ Hiboot Application Framework
/_/ /_/ /_/ /_.___/\____/\____/\__/ https://hiboot.hidevops.io
[INFO] Starting Hiboot web application on localhost with PID xxx
[INFO] Mapped "/user" onto controller.userController.Post()
[INFO] Mapped "/user/id/{id}" onto controller.userController.GetById()
[INFO] Mapped "/user/all" onto controller.userController.GetAll()
[INFO] Mapped "/health" onto actuator.healthController.Get()
[INFO] Hiboot started on port(s) http://localhost:8080
发送请求
创建用户
curl -X POST http://localhost:8080/user \
-H "Content-Type: application/json" \
-d '{"name":"张三","username":"zhangsan","password":"secret123","email":"zhangsan@example.com","age":30,"gender":1}'
获取所有用户
curl http://localhost:8080/user/all
响应:
{
"code": 200,
"data": [
{
"id": 209536579658580081,
"name": "张三",
"username": "zhangsan",
"email": "zhangsan@example.com",
"age": 30,
"gender": 1
}
],
"message": "Success"
}
单元测试
main_test.go
package main
import (
"testing"
"time"
)
func TestRunMain(t *testing.T) {
go main()
time.Sleep(200 * time.Millisecond)
}
使用 Mock 进行控制器测试
使用 Mockery 生成 Mock 实现:
go install github.com/vektra/mockery/v2@latest
mockery --name UserService --dir service --output service/mocks
然后编写测试:
package controller
import (
"myapp/entity"
"myapp/service/mocks"
"net/http"
"testing"
"github.com/hidevopsio/hiboot/pkg/app/web"
"github.com/hidevopsio/hiboot/pkg/utils/idgen"
"github.com/stretchr/testify/assert"
)
func TestUserController(t *testing.T) {
mockUserService := new(mocks.UserService)
userController := newUserController(mockUserService)
testApp := web.RunTestApplication(t, userController)
id, err := idgen.Next()
assert.NoError(t, err)
testUser := &entity.User{
Id: id,
Name: "测试用户",
Username: "testuser",
Password: "password123",
Email: "test@example.com",
Age: 25,
Gender: 1,
}
mockUserService.On("AddUser", testUser).Return(nil)
t.Run("POST 请求应该成功添加用户", func(t *testing.T) {
testApp.Post("/user").
WithJSON(testUser).
Expect().Status(http.StatusOK)
})
mockUserService.On("GetUser", id).Return(testUser, nil)
t.Run("GET 请求应该成功获取用户", func(t *testing.T) {
testApp.Get("/user/id/{id}").
WithPath("id", id).
Expect().Status(http.StatusOK)
})
mockUserService.AssertExpectations(t)
}