Comprehensive Example
This example demonstrates a simple implementation of APIs for the following functionalities:
- Retrieving book data (pagination)
- Adding, editing, and deleting book data
- User login with username and password
- Displaying and managing book data requires login
The example uses APIs to return data in JSON format only. If template implementation is needed, the core parts remain the same, with the data returned organized into template pages.
1. Create Project
gf init gfbook
Delete the automatically generated files in api
and controller
, and remove the route bindings in cmd.go
.
Add the required libraries, mysql
driver, and jwt
.
Method 1 (requires internet connection):
go get github.com/gogf/gf/contrib/drivers/mysql/v2
go get github.com/golang-jwt/jwt/v5
Initialize the mysql
driver import
main.go
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
)
Method 2 (requires previously downloaded versions of the libraries, no internet needed)
Initial import is required, otherwise addition is not possible.
main.go
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
_ "github.com/golang-jwt/jwt/v5" // Remove this line after running go mod tidy
)
go.mod
require (
github.com/gogf/gf/contrib/drivers/mysql/v2 v2.5.3
github.com/gogf/gf/v2 v2.5.3
github.com/golang-jwt/jwt/v5 v5.0.0
)
Run the following command to update
go mod tidy
2. Configuration
internal/config/config.yaml
Configure database connection
server:
address: ":8000"
openapiPath: "/api.json"
swaggerPath: "/swagger"
serverRoot: "resource/public/resource"
database:
link: "mysql:root:root@tcp(127.0.0.1:3306)/goframe?loc=Local&parseTime=true"
logger:
level : "all"
stdout: true
Prepare the required resource files
hack/config.yaml
Configure DAO generation information
gfcli:
gen:
dao:
link: "mysql:root:root@tcp(127.0.0.1:3306)/goframe"
tables: "user,book"
jsonCase: "Snake"
After configuration, run the command to generate DAO related files
gf gen dao
3.Define API Data Structures
api/user/user.go
package user
import "github.com/gogf/gf/v2/frame/g"
type LoginReq struct {
g.Meta `path:"/login" method:"post"`
Username string `p:"username" v:"required#Please enter username" dc:"Username"`
Password string `p:"password" v:"required#Please enter password" dc:"Password"`
}
type UserInfo struct {
Id uint `json:"id" dc:"User ID"`
Username string `json:"username" dc:"Username"`
Nickname string `json:"nickname" dc:"Nickname"`
Avatar string `json:"avatar" dc:"User Avatar"`
}
type LoginRes struct {
Token string `json:"token" dc:"Validation token"`
UserInfo *UserInfo `json:"user_info"`
}
api/book/book.go
package book
import (
"gfbook/internal/model/entity"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
type IndexReq struct {
g.Meta `path:"/book" method:"get"`
Page uint `p:"page" v:"required|integer|min:1#数据页不能为空|数据页只能是整数|数据页不能小于1" dc:"页数"`
PageSize uint `p:"page_size" v:"integer|min:1#数据大小只能为整数|数据大小不能小于1" d:"10" dc:"每页数据量"`
}
type IndexRes struct {
Page uint `json:"page" dc:"页数"`
PageSize uint `json:"page_size" dc:"每页数据量"`
Rows []entity.Book `json:"rows" dc:"查询数据"`
}
type AddReq struct {
g.Meta `path:"/book" method:"post"`
Name string `p:"name" v:"required#书名不能为空" dc:"书名" `
Author string `p:"author" v:"required#作者不能为空" dc:"作者" `
Price float64 `p:"price" v:"required|float|min:0#价格不能为空|价格格式不正确|价格不能小于0" dc:"价格" `
PublishTime *gtime.Time `p:"publish_time" v:"date#出版时间格式不正确" dc:"出版日期"`
}
type AddRes struct {
}
type EditReq struct {
g.Meta `path:"/book" method:"put"`
Id uint `p:"id" v:"required" dc:"书本ID"`
Name string `p:"name" dc:"书名" `
Author string `p:"author" dc:"作者" `
Price float64 `p:"price" v:"float|min:0#价格格式不正确|价格不能小于0" dc:"价格" `
PublishTime *gtime.Time `p:"publish_time" v:"date#出版时间格式不正确" dc:"出版日期"`
}
type EditRes struct{}
type DelReq struct {
g.Meta `path:"/book" method:"delete"`
Id uint `p:"id" v:"required" dc:"书本ID"`
}
type DelRes struct{}
4. Controller and route binding
internal/controller/user/user.go
package user
import (
"context"
"gfbook/api/user"
)
var UserController = &cUser{}
type cUser struct {
}
func (c *cUser) Login(ctx context.Context, req *user.LoginReq) (res *user.LoginRes, err error) {
return
}
internal/controller/book/book.go
package book
import (
"context"
"gfbook/api/book"
)
var BookController = &cBook{}
type cBook struct{}
func (c *cBook) Index(ctx context.Context, req *book.IndexReq) (res *book.IndexRes, err error) {
return
}
func (c *cBook) Add(ctx context.Context, req *book.AddReq) (res *book.AddRes, err error) {
return
}
func (c *cBook) Edit(ctx context.Context, req *book.EditReq) (res *book.EditRes, err error) {
return
}
func (c *cBook) Del(ctx context.Context, req *book.DelReq) (res *book.EditRes, err error) {
return
}
internal/cmd/cmd.go
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Group("/user", func(group *ghttp.RouterGroup) {
group.Bind(user.UserController)
})
group.Group("/", func(group *ghttp.RouterGroup) {
group.Bind(book.BookController)
})
})
5. Design the Service interface of Book and improve the controller
internal/service/book.go
package service
import (
"context"
"gfbook/internal/model/do"
"gfbook/internal/model/entity"
)
type IBook interface {
// 查询书本列表
GetList(ctx context.Context, page uint, pageSize uint) (books []entity.Book, err error)
// 添加书本信息
Add(ctx context.Context, book do.Book) (err error)
// 修改书本信息
Edit(ctx context.Context, book do.Book) (err error)
// 删除书本信息
Del(ctx context.Context, id uint) (err error)
}
var localBook IBook
func Book() IBook {
if localBook == nil {
panic("IBook接口未实现或未注册")
}
return localBook
}
func RegisterBook(i IBook) {
localBook = i
}
internal/controller/book/book.go
package book
import (
"context"
"gfbook/api/book"
"gfbook/internal/model/do"
"gfbook/internal/service"
)
var BookController = &cBook{
service: service.Book(),
}
type cBook struct {
service service.IBook
}
func (c *cBook) Index(ctx context.Context, req *book.IndexReq) (res *book.IndexRes, err error) {
books, err := service.Book().GetList(ctx, req.Page, req.PageSize)
if err == nil {
res = &book.IndexRes{
Page: req.Page,
PageSize: req.PageSize,
Rows: books,
}
}
return
}
func (c *cBook) Add(ctx context.Context, req *book.AddReq) (res *book.AddRes, err error) {
err = c.service.Add(ctx, do.Book{
Name: req.Name,
Author: req.Author,
Price: req.Price,
PublishTime: req.PublishTime,
})
return
}
func (c *cBook) Edit(ctx context.Context, req *book.EditReq) (res *book.EditRes, err error) {
err = c.service.Edit(ctx, do.Book{
Id: req.Id,
Name: req.Name,
Author: req.Author,
Price: req.Price,
PublishTime: req.PublishTime,
})
return
}
func (c *cBook) Del(ctx context.Context, req *book.DelReq) (res *book.EditRes, err error) {
err = c.service.Del(ctx, req.Id)
return
}
6. Specific implementation of Book operation in the Logic layer
- 新建
internal/logic/book/book.go
文件,实现上面的IBook
接口
internal/logic/book/book.go
package book
import (
"context"
"gfbook/internal/dao"
"gfbook/internal/model/do"
"gfbook/internal/model/entity"
"gfbook/internal/service"
)
func init() {
service.RegisterBook(New())
}
func New() *iBook {
return &iBook{}
}
type iBook struct{}
// Add implements service.IBook.
func (*iBook) Add(ctx context.Context, book do.Book) (err error) {
_, err = dao.Book.Ctx(ctx).Data(book).Insert()
return
}
// Del implements service.IBook.
func (*iBook) Del(ctx context.Context, id uint) (err error) {
_, err = dao.Book.Ctx(ctx).Where(dao.Book.Columns().Id, id).Delete()
return
}
// Edit implements service.IBook.
func (*iBook) Edit(ctx context.Context, book do.Book) (err error) {
_, err = dao.Book.Ctx(ctx).Where(dao.Book.Columns().Id, book.Id).Data(book).Update()
return
}
// GetList implements service.IBook.
func (*iBook) GetList(ctx context.Context, page uint, pageSize uint) (books []entity.Book, err error) {
err = dao.Book.Ctx(ctx).Page(int(page), int(pageSize)).Scan(&books)
return
}
- Create a new file
internal/logic/logic.go
and initialize and import the above implementation
import (
_ "gfbook/internal/logic/book"
)
- Initialize and import logic in
main.go
main.go
import (
_ "gfbook/internal/logic"
)
7. User's Service layer design and controller improvement
internal/service/user.go
package service
import (
"context"
"gfbook/internal/model/entity"
)
type IUser interface {
Login(ctx context.Context, username string, password string) (user *entity.User, err error)
}
var localUser IUser
func User() IUser {
if localUser == nil {
panic("IUser接口未实现或未注册")
}
return localUser
}
func RegisterUser(i IUser) {
localUser = i
}
internal/controller/user/user.go
package user
import (
"context"
api_user "gfbook/api/user"
"gfbook/internal/model/entity"
"gfbook/internal/service"
"time"
"github.com/golang-jwt/jwt/v5"
)
var UserController = &cUser{}
type cUser struct {
}
func (c *cUser) Login(ctx context.Context, req *api_user.LoginReq) (res *api_user.LoginRes, err error) {
user, err := service.User().Login(ctx, req.Username, req.Password)
if err == nil {
res = &api_user.LoginRes{
Token: jwtToken(user),
UserInfo: &api_user.UserInfo{
Id: user.Id,
Username: user.Username,
Nickname: user.Nickname,
Avatar: user.Avatar,
},
}
}
return
}
func jwtToken(user *entity.User) string {
const JwtTokenKey = "zbvxMPp12fibPNnOaYmc0rviniaJCOVJ"
// 实际使用中可将Key存于文件中或放在常量中 consts.JwtTokenKey
claim := jwt.RegisteredClaims{
Subject: user.Username,
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)),
}
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claim).SignedString([]byte(JwtTokenKey))
if err != nil {
panic("token生成出错")
}
return token
}
8. User's Logic layer implementation
internal/logic/user/user.go
package user
import (
"context"
"gfbook/internal/dao"
"gfbook/internal/model/do"
"gfbook/internal/model/entity"
"gfbook/internal/service"
"github.com/gogf/gf/v2/errors/gerror"
)
type iUser struct{}
func New() *iUser {
return &iUser{}
}
func init() {
service.RegisterUser(New())
}
// Login implements service.IUser.
func (*iUser) Login(ctx context.Context, username string, password string) (user *entity.User, err error) {
err = dao.User.Ctx(ctx).Where(do.User{
Username: username,
Password: password,
}).Scan(&user)
if user == nil {
err = gerror.New("用户名或密码有误")
}
return
}
internal/logic/logic.go
import (
_ "gfbook/internal/logic/book"
_ "gfbook/internal/logic/user"
)
9. Add login verification middleware
internal/service/middware.go
package service
import "github.com/gogf/gf/v2/net/ghttp"
type IMiddleware interface {
Auth(r *ghttp.Request)
}
var localMiddleware IMiddleware
func Middleware() IMiddleware {
if localMiddleware == nil {
panic("IMiddleware接口未实现或未注册")
}
return localMiddleware
}
func RegisterMiddleware(i IMiddleware) {
localMiddleware = i
}
internal/logic/middleware/middleware.go
package middleware
import (
"gfbook/internal/consts"
"gfbook/internal/service"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/golang-jwt/jwt/v5"
)
func init() {
service.RegisterMiddleware(New())
}
func New() *iMiddleware {
return &iMiddleware{}
}
type iMiddleware struct {
}
// Auth implements service.IMiddleware.
func (*iMiddleware) Auth(r *ghttp.Request) {
var res *ghttp.DefaultHandlerResponse
tokenStr := r.Header.Get("Authorization")
if tokenStr == "" {
res = &ghttp.DefaultHandlerResponse{
Code: 403,
Message: "请登录后携带token访问",
}
} else {
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
return []byte(consts.JwtTokenKey), nil
})
if err != nil || !token.Valid {
res = &ghttp.DefaultHandlerResponse{
Code: 403,
Message: "token已失效,请重新登录",
}
}
}
if res != nil {
r.Response.WriteJsonExit(res)
}
r.Middleware.Next()
}
internal/logic/logic.go
package logic
import (
_ "gfbook/internal/logic/book"
_ "gfbook/internal/logic/middleware"
_ "gfbook/internal/logic/user"
)
internal/cmd/cmd.go
group.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(service.Middleware().Auth)
group.Bind(book.BookController)
})
10. API Interface documentation
Prepare the corresponding JS and CSS files and put them into the directory corresponding to the static resources, or you can use the online CDN link
template/api.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/resource/css/swagger-ui.css">
<script src="/resource/js/swagger-ui-bundle.js"></script>
<title>API Doc</title>
</head>
<body>
<div id="swagger-ui"></div>
</body>
</html>
<script type="text/javascript">
window.ui = SwaggerUIBundle({
url: '/api.json',
dom_id: '#swagger-ui'
})
</script>
internal/cmd/cmd.go
group.GET("/api", func(req *ghttp.Request) {
req.Response.WriteTpl("/api.html")
})