# Web 项目

# 安装gone辅助工具

go install github.com/gone-io/gone/tools/gone@latest

安装可以使用gone命令:

gone -h
	➜  demo gone -h
	NAME:
	gone - A new cli application
	USAGE:
	gone [global options] command [command options] [arguments...]
	DESCRIPTION:
	generate gone code or generate gone app
	COMMANDS:
	priest   -s ${scanPackageDir} -p ${pkgName} -f ${funcName} -o ${outputFilePath} [-w]
	mock     -f ${fromGoFile} -o ${outGoFile}
	create   [-t ${template} [-m ${modName}]] ${appName}
	help, h  Shows a list of commands or help for one command
	GLOBAL OPTIONS:
	--help, -h  show help (default: false)

支持的功能:

  1. create,创建一个gone app,暂时只支持创建web app
  2. priest,为项目自动生成 Priest 函数,了解更多
  3. 生成用于测试的mock代码

# 创建一个web项目并运行代码

# 创将一个名为 web-app 的项目
gone create web-app
cd web-app
make run

# 项目结构

➜  xxx tree
.
├── Dockerfile
├── Makefile
├── README.md
├── cmd
│   └── server
│       └── main.go
├── config
│   ├── default.properties
│   ├── dev.properties
│   ├── local.properties
│   └── prod.properties
├── docker-compose.yaml
├── go.mod
├── internal
│   ├── controller
│   │   └── demo_ctr.go
│   ├── interface
│   │   ├── domain
│   │   │   ├── demo.go
│   │   │   └── user.go
│   │   └── service
│   │       └── i_demo.go
│   ├── master.go
│   ├── middleware
│   │   ├── authorize.go
│   │   └── pub.go
│   ├── module
│   │   └── demo
│   │       ├── demo_svc.go
│   │       └── error.go
│   ├── pkg
│   │   └── utils
│   │       └── error.go
│   └── router
│       ├── auth_router.go
│       └── pub_router.go
└── tests
    └── api
        └── demo.http
17 directories, 23 files
  • cmd/server/main.go: 启动文件,main函数所在文件
  • config/: 项目配置文件目录,支持.properties文件
  • internal/router/: 在该目录定义路由器
  • internal/middleware/: 中间件目录,如果需要定义web中间件,在该目录编写
  • internal/controller/: controller目录,在该目录中的文件定义路由
  • internal/interface/service/: 该目录放服务的接口定义
  • internal/domain/: 该目录放领域对象
  • internal/entity/: 该目录放一些无逻辑的结构体,类似于Java 的POJO
  • internal/module/: 模块目录,下面的每一个子目录实现一个模块的功能,一般是internal/interface/service/中定义的服务的业务实现;
  • internal/pkg/: 在该目录可以放一些项目共用的工具代码
  • internal/master.go: 存放MasterPriest函数
  • internal/priest.go: gone priest 命令生成的 Priest函数,用于注册所有Goner

# Router

在目录internal/router中分别实现了两个gin.IRouter:

  • pubRouter,公开的路由,挂载在该路由下的接口,请求将无需授权即可访问。
  • authRouter,鉴权的路由,挂载在该路由下的接口,请求必须先要经过授权。

我们来分析internal/router/pub_router.go的代码:

package router
import (
	"web-app/internal/middleware"
	"github.com/gone-io/gone"
	"github.com/gone-io/gone/goner/gin"
)
const IdRouterPub = "router-pub"
//go:gone
func NewPubRouter() (gone.Goner, gone.GonerId) {
	return &pubRouter{}, IdRouterPub
}
type pubRouter struct {
	gone.Flag
	gin.IRouter
	root gin.IRouter               `gone:"gone-gin-router"`
	pub  *middleware.PubMiddleware `gone:"*"`
}
func (r *pubRouter) AfterRevive() gone.AfterReviveError {
	r.IRouter = r.root.Group("/api", r.pub.Next)
	return nil
}
  1. 对于router,需要实现了gin.IRouter接口中定义的方法;
  2. 结构体pubRouter内嵌了一个gin.IRouter,就等于直接实现了gin.IRouter接口,只在AfterRevive()中将其赋予一个值就可以了;
  3. r.IRouter = r.root.Group("/api", r.pub.Next)意思是当前路由是根路由下/api的子路由,并且默认的增加了一个中间r.pub.Next
  4. root gin.IRouter是一个Gone框架中github.com/gone-io/gone/goner/gin包提供的一个框架级Goner,具名注入的gone-gin-router

# Controller

下面是Controller接口的定义:

// Controller 控制器接口,由业务代码编码实现,用于挂载和处理路由
// 使用方式参考 [示例代码](https://gitlab.openviewtech.com/gone/gone-example/-/tree/master/gone-app)
type Controller interface {
	// Mount   路由挂载接口,改接口会在服务启动前被调用,该函数的实现通常情况应该返回`nil`
	Mount() MountError
}

编写http接口,我们需要实现Controller接口,在Mount方法中实现接口路由的挂载,如internal/controller/demo_ctr.go的代码:

package controller
import (
	"web-app/internal/interface/service"
	"web-app/internal/pkg/utils"
	"github.com/gone-io/gone"
	"github.com/gone-io/gone/goner/gin"
)
//go:gone
func NewDemoController() gone.Goner {
	return &demoController{}
}
type demoController struct {
	gone.Flag
	demoSvc service.IDemo `gone:"*"`  //注入依赖的服务
	authRouter gin.IRouter `gone:"router-auth"`  //注入路由器
	pubRouter  gin.IRouter `gone:"router-pub"`   //注入路由器
}
func (ctr *demoController) Mount() gin.MountError {
	//需要鉴权的路由分组
	ctr.
		authRouter.
		Group("/demo").
		GET("/show", ctr.showDemo)
	//不需要鉴权的路由分组
	ctr.
		pubRouter.
		Group("/demo2").
		GET("/show", ctr.showDemo).
		GET("/error", ctr.error).
		GET("/echo", ctr.echo)
	return nil
}
func (ctr *demoController) showDemo(ctx *gin.Context) (any, error) {
	return ctr.demoSvc.Show()
}
func (ctr *demoController) error(ctx *gin.Context) (any, error) {
	return ctr.demoSvc.Error()
}
func (ctr *demoController) echo(ctx *gin.Context) (any, error) {
	type Req struct {
		Echo string `form:"echo"`
	}
	var req Req
	if err := ctx.Bind(&req); err != nil {
		return nil, gin.NewParameterError(err.Error(), utils.ParameterParseError)
	}
	return ctr.demoSvc.Echo(req.Echo)
}

# Service

规范上,我们要求将服务的接口定义在internal/interface/service目录,文件名以i_打头,接口类型以I打头,例如: 文件:i_demo.go

package service
import "web-app/internal/interface/domain"
type IDemo interface {
	Show() (*domain.DemoEntity, error)
	Echo(input string) (string, error)
	Error() (any, error)
}

服务的逻辑实现,放到internal/module目录分模块实现。

# 数据库操作

在下一个例子中,我们将构建使用MySQL作为数据的Web项目。