目录
前言
例如:习惯了java springboot 开发方式,比较疑惑golang web开发的流程和模块化的区分,
就golang web整个后台模块做下梳理。
一、golang web是什么?
golang web是指 web后台HTTP接口开发模块。
二、搭建流程
1.模块划分
project
-app 通常是程序启动后需要加载的数据
app.go --加载gin 路由,指向 controller,并且做了拦截 heard token,解析放入上下文
-biz
--model
--dto --请求的struct
--entity --实体类
--service
-common 公共常量,定义返回值 struct
common.go
-conf 配置信息,从配置文件里面读取到 struct
config.go //根据环境变量读取环境resorce配置信息
-controller 访问入口
account_controller.go
-resource 配置文件
app.yml 配置数据库信息,根据 环境变量获取 取哪个配置文件
app-dev.yml
app-test.yml
app-prod.yml
-util
token_util.go 生成和解析token
-test 测试包,单元测试文件
Dockerfile docker打包文件
go.mod -go包管理
main.go 启动入口
2.详细开发步骤
相信大家看完前面的包,也有很多疑惑,该怎么开始呢?
现在就一个包,一个包,把代码拆开来
- main.go 文件
import (
"fmt"
"app"
"log"
)
func main() {
//调用 app包里面的启动
err := app.Run()
if err != nil {
log.Fatal(err)
}
fmt.Println("go web start ..!")
}
- 2.app包, 只有一个app.go 文件
- 使用gin路由,指向contoller包
- 使用util 包里面的解析 head里面的token,并把 解析后的userId,放入用户信息
- 处理跨域请求
- 初始化 dao 数据库连接
package app
import (
"errors"
"fmt"
// 引入gin web插件
"github.com/gin-gonic/gin"
//引入mysql插件
_ "github.com/go-sql-driver/mysql"
//引入 http插件
"net/http"
//项目 自定义包
"project/biz/dao"
"project/biz/service"
"ibc/common"
"project/controller"
"project/util"
)
func Run() error {
//初始化数据库连接
dao.InitDB()
defer dao.CloseDB()
//创建service
accountService := service.NewAccountService()
activitiesService := service.NewOfflineActivitiesService()
//定义路由
router := gin.Default()
router.GET("/", homeEndpoint)
accountGroup := router.Group("/account")
{
accountCtl := controller.NewAccountController(accountService)
accountGroup.POST("/", wrapHandler(accountCtl.InsertAccount))
accountGroup.GET("/:userId", wrapHandler(accountCtl.FindAccount))
accountGroup.GET("/account-flow/:accountId", wrapHandler(accountCtl.FindAccountFlow))
}
return router.Run(":8080")
}
type EndpointFunc func(*gin.Context) (any, error)
//定义返回
func homeEndpoint(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": "SUCCESS"})
}
// 定义统一处理器
func wrapHandler(handler EndpointFunc) gin.HandlerFunc {
return func(c *gin.Context) {
// 处理跨域请求
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
Cors(c)
//从header 获取token,
var token = c.GetHeader("token")
//解析token,如果token不存在,或者 解析错误,则返回 没权限
userId, err := util.TokenHandle(token)
if err != nil {
fmt.Errorf(err.Error())
c.JSON(http.StatusBadRequest, gin.H{"code": "ERROR", "message": "token is invalid"})
return
}
//解析后 userId 放入上下文中,后面的service服务,可以获取该用户信息
c.Set("userId", userId)
data, err := handler(c)
if err != nil {
var systemErr *common.Error
if errors.As(err, &systemErr) {
c.JSON(systemErr.Status, common.NewErrorResult(systemErr.Code, systemErr.Msg))
} else {
c.JSON(http.StatusBadRequest, gin.H{"code": "ERROR", "message": err.Error()})
}
return
}
c.JSON(http.StatusOK, gin.H{"code": "SUCCESS", "data": data})
}
}
//跨域请求处理
func Cors(context *gin.Context) {
method := context.Request.Method
// 必须,接受指定域的请求,可以使用*不加以限制,但不安全
//context.Header("Access-Control-Allow-Origin", "*")
context.Header("Access-Control-Allow-Origin", context.GetHeader("Origin"))
fmt.Println(context.GetHeader("Origin"))
// 必须,设置服务器支持的所有跨域请求的方法
context.Header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
// 服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段
context.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Token")
// 可选,设置XMLHttpRequest的响应对象能拿到的额外字段
context.Header("Access-Control-Expose-Headers", "Access-Control-Allow-Headers, Token")
// 可选,是否允许后续请求携带认证信息Cookir,该值只能是true,不需要则不设置
context.Header("Access-Control-Allow-Credentials", "true")
// 放行所有OPTIONS方法
if method == "OPTIONS" {
context.AbortWithStatus(http.StatusNoContent)
return
}
context.Next()
}
2.dao包
- 初始化连接
- 业务dao层
package dao import ( "fmt" //引入 sqlx "github.com/jmoiron/sqlx" //项目内的 配置信息 "project/conf" "log" ) var BaseDb sqlx.DB // app.go 里面调用 func InitDB() { datasource := fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true&charset=utf8", conf.Config.Mysql.User, conf.Config.Mysql.Passwd, conf.Config.Mysql.Host, conf.Config.Mysql.DBName) db, err := sqlx.Open("mysql", datasource) if err != nil { log.Fatal(err) return } BaseDb = *db } // app.go 里面调用 func CloseDB() { err := BaseDb.Close() if err != nil { log.Fatal(err) } }
package dao import ( "database/sql" "fmt" "project/biz/model/entity" "strings" ) type UserAccountRepository interface { Insert(account entity.UserAccount) error Update(account entity.UserAccount) error DeleteById(id string) (bool, error) FindById(userId string) (entity.UserAccount, error) ListAll() ([]entity.UserAccount, error) ExistsByUserId(userId string) (error, bool) } type UserAccountFlowRepository interface { Insert(accountFlow entity.UserAccountFlow) (int, error) DeleteById(id string) (bool, error) FindById(id string) (entity.UserAccountFlow, error) ListAll() ([]entity.UserAccountFlow, error) ListByAccountId(accountId string) (*[]entity.UserAccountFlow, error) FindByActivities(id string, id2 int64) (*[]entity.UserAccountFlow, error) } type accountRepo struct { } type accountFlowRepo struct { } //NewAccountRepository 申明实现了 UserAccountRepository 接口 func NewAccountRepository() UserAccountRepository { return &accountRepo{} } //Insert < 具体实现 NewAccountRepository func (u accountRepo) Insert(account entity.UserAccount) error { sql := `insert into user_account(id,user_id,blockchain_address,balance)values(:id,:user_id,:blockchain_address,:balance)` result, err := BaseDb.NamedExec(sql, &account) if err != nil { return err } fmt.Println(result) return nil } func (u accountRepo) ExistsByUserId(userId string) (error, bool) { sqlStr := `select * from user_account where user_id=?` var account entity.UserAccount err := BaseDb.Get(&account, sqlStr, userId) if err == sql.ErrNoRows { return nil, false } if err != nil { return err, false } if account == (entity.UserAccount{}) { return nil, true } return nil, false } //Update < 具体实现 NewAccountRepository func (u accountRepo) Update(account entity.UserAccount) error { sqlColum := `update user_account set ` if account.Balance >= -1 { sqlColum += ` balance =:balance ,` } if account.BlockchainAddress != "" { sqlColum += ` blockchain_address =:blockchain_address ,` } sqlColum = strings.TrimSuffix(sqlColum, ",") sqlColum += " where id =:id " _, err := BaseDb.NamedExec(sqlColum, &account) return err } //DeleteById < 具体实现 NewAccountRepository func (u accountRepo) DeleteById(id string) (bool, error) { sql := "update user_account set is_delete=1 where id =?" _, err := BaseDb.Exec(sql, id) if err != nil { return false, err } return true, err } //FindById < 具体实现 NewAccountRepository func (u accountRepo) FindById(userId string) (entity.UserAccount, error) { sql := "select * from user_account where user_id=?" var userAccount entity.UserAccount err := BaseDb.Get(&userAccount, sql, userId) if err != nil { return userAccount, err } return userAccount, err } //ListAll < 具体实现 NewAccountRepository func (u accountRepo) ListAll() ([]entity.UserAccount, error) { sql := "select * from user_account" var accountFlow []entity.UserAccount err := BaseDb.Get(&accountFlow, sql) return accountFlow, err } func NewAccountFlowRepository() UserAccountFlowRepository { return &accountFlowRepo{} } //Insert < 具体实现 NewAccountFlowRepository func (accountFlowRepo) Insert(account entity.UserAccountFlow) (int, error) { sql := `insert into user_account_flow(user_id,account_id,type,amount,blockchain_address,before_balance,balance,activities_id,transaction_info) values(:user_id,:account_id,:type,:amount,:blockchain_address,:before_balance,:balance,:activities_id,:transaction_info)` result, err := BaseDb.NamedExec(sql, account) if err != nil { return 0, err } id, err := result.LastInsertId() if err != nil { return 0, err } account.Id = id return int(id), nil } //DeleteById < 具体实现 NewAccountFlowRepository func (accountFlowRepo) DeleteById(id string) (bool, error) { sql := "update user_account_flow set is_delete=1 where id =?" _, err := BaseDb.Exec(sql, id) if err != nil { return false, err } return true, err } //FindById < 具体实现 NewAccountFlowRepository func (accountFlowRepo) FindById(id string) (entity.UserAccountFlow, error) { sql := "select * from user_account_flow where id=?" var userAccount entity.UserAccountFlow err := BaseDb.Get(&userAccount, sql, id) if err != nil { return userAccount, err } return userAccount, err } //ListAll < 具体实现 NewAccountFlowRepository func (accountFlowRepo) ListAll() ([]entity.UserAccountFlow, error) { sql := "select * from user_account_flow where is_delete =0" var accountFlow []entity.UserAccountFlow err := BaseDb.Get(&accountFlow, sql) return accountFlow, err } //ListByAccountId < 具体实现 NewAccountFlowRepository func (accountFlowRepo) ListByAccountId(accountId string) (*[]entity.UserAccountFlow, error) { sqlStr := "select * from user_account_flow where is_delete =0 and account_id =?" var accountFlow []entity.UserAccountFlow err := BaseDb.Select(&accountFlow, sqlStr, accountId) if err == sql.ErrNoRows { return nil, nil } return &accountFlow, err } func (accountFlowRepo) FindByActivities(accountId string, activitiesId int64) (*[]entity.UserAccountFlow, error) { sqlStr := "select * from user_account_flow where is_delete =0 and account_id =? and activities_id=?" var accountFlow []entity.UserAccountFlow err := BaseDb.Select(&accountFlow, sqlStr, accountId, activitiesId) if err == sql.ErrNoRows { return nil, nil } return &accountFlow, err }
3.service
-
package service import ( "database/sql" "errors" "fmt" "project/dao" "project/biz/model/dto" "project/biz/model/entity" "project/util" "math" "strconv" "strings" ) type UserAccountService interface { // InsertAccount 新增账户信息 InsertAccount(accountReq dto.CreateAccountRequest) (*entity.UserAccount, error) // FindAccount 查找账户信息 FindAccount(userId string) (any, error) // FindAccountFlow 根据账户ID查找账户 FindAccountFlow(accountId string) ([]entity.UserAccountFlow, error) //RewardAccount 扫码奖励 RewardAccount(scanning dto.Scanning) (any, error) } func NewAccountService() UserAccountService { return &accountService{ AccountRepo: dao.NewAccountRepository(), AccountFlowRepo: dao.NewAccountFlowRepository(), OfflineActivitiesRepo: dao.NewOfflineActivitiesRepo(), } } type accountService struct { AccountRepo dao.UserAccountRepository AccountFlowRepo dao.UserAccountFlowRepository OfflineActivitiesRepo dao.OfflineActivitiesRepository } // InsertAccount 新增账户信息 func (me *accountService) InsertAccount(accountDto dto.CreateAccountRequest) (*entity.UserAccount, error) { err, b := me.AccountRepo.ExistsByUserId(accountDto.UserId) if err != nil { return nil, err } if b { return nil, err } node, _ := util.NewWorker(10) account := entity.UserAccount{ UserId: accountDto.UserId, Id: "AC" + strconv.Itoa(int(node.GetId())), BlockchainAddress: accountDto.Address, } err = me.AccountRepo.Insert(account) if err != nil { return nil, err } return &account, nil } // FindAccount 查找账户信息 func (me *accountService) FindAccount(userId string) (any, error) { account, err := me.AccountRepo.FindById(userId) if err == sql.ErrNoRows { accDto := dto.CreateAccountRequest{} accDto.UserId = userId address, err := indiev_wallet.GetAddress() if err != nil { return nil, err } accDto.Address = address accountEntity, err := me.InsertAccount(accDto) if err != nil { return nil, err } return accountEntity, nil } if account.BlockchainAddress != "" { chainBalance := util.GetBalance(account.BlockchainAddress) chainBalance = strings.TrimLeft(chainBalance, "0x") f, err := strconv.ParseUint(chainBalance, 16, 32) if err != nil { return nil, err } //金额转换 account.Balance = int64(float64(f) / math.Pow(10, 8)) } return account, err } // FindAccountFlow 根据账户ID查找账户 func (me *accountService) FindAccountFlow(accountId string) ([]entity.UserAccountFlow, error) { accountFlows, err := me.AccountFlowRepo.ListByAccountId(accountId) if err != nil { return nil, err } return *accountFlows, nil } type scanningDto struct { RewardValue int64 `json:"rewardValue" db:"reward_value"` Unit string `json:"unit" db:"reward_value"` }
4.controller包
-
package controller import ( "github.com/gin-gonic/gin" //项目 "project/biz/model/dto" "project/biz/service" ) func NewAccountController(accountService service.UserAccountService) *AccountController { return &AccountController{ accountService: accountService, } } // InsertAccount 新增账户信息 func (me *AccountController) InsertAccount(c *gin.Context) (any, error) { var req dto.CreateAccountRequest err := c.ShouldBindJSON(&req) if err != nil { return nil, err } account, err := me.accountService.InsertAccount(req) if err != nil { return nil, err } return account, err } // FindAccount 查找账户信息 func (me *AccountController) FindAccount(c *gin.Context) (any, error) { userId := c.Param("userId") account, err := me.accountService.FindAccount(userId) if err != nil { return nil, err } return account, err } // FindAccountFlow 根据账户ID查找账户 func (me *AccountController) FindAccountFlow(c *gin.Context) (any, error) { accountId := c.Param("accountId") account, err := me.accountService.FindAccountFlow(accountId) if err != nil { return nil, err } return account, err } type AccountController struct { accountService service.UserAccountService }
5.common包
-
返回struct
-
package common import "net/http" const ( CodeSuccess = "SUCCESS" CodeError = "ERROR" ParamFormatError = "PARAM_FORMAT_ERROR" ParamNotExistError = "PARAM_NOT_EXIST_ERROR" AppNotExistError = "APP_NOT_EXIST_ERROR" AppExistError = "APP_EXIST_ERROR" InternalServiceErr = "INTERNAL_SERVICE_ERROR" Unauthorized = "UNAUTHORIZED" ) type Result struct { Code string `json:"code"` Data any `json:"data"` Message string `json:"message"` } func NewOkResult(msg string) *Result { return &Result{ Code: CodeSuccess, Data: nil, Message: msg, } } func NewDataResult(data any) *Result { return &Result{ Code: CodeSuccess, Data: data, Message: "", } } func NewErrorResult(code, msg string) *Result { return &Result{ Code: code, Data: nil, Message: msg, } } type Error struct { Status int Code string Msg string } func BadRequestErr(code string, msg string) error { return &Error{ Status: http.StatusBadRequest, Code: code, Msg: msg, } } func NotFoundErr(code string, msg string) error { return &Error{ Status: http.StatusNotFound, Code: code, Msg: msg, } } func UnauthorizedErr() error { return &Error{ Status: http.StatusUnauthorized, Code: "Unauthorized", Msg: "", } } func InternalServerErr(code string, msg string) error { return &Error{ Status: http.StatusInternalServerError, Code: code, Msg: msg, } } func (e *Error) Error() string { return e.Msg }
6.config包
-
package conf import ( log "github.com/sirupsen/logrus" "github.com/spf13/viper" "os" ) //数据库配置 type DbConfig struct { Host string User string Passwd string DBName string } type Struct struct { Mysql DbConfig } var Config Struct //初始化,优先加载 func init() { var appName = "app" //路径 viper.AddConfigPath("./resource") //如果 环境信息,或者命令行有 这个变量,则追加环境信息,如果没有,默认取 app.yml //加载了环境则是 app-dev.yml |app-test.yml configEnv := os.Getenv("GO_ENV") if configEnv != "" { appName += "-" + configEnv } viper.SetConfigName(appName) if err := viper.ReadInConfig(); err != nil { log.Panicf("Error reading config file %s\n", err) } // viper读取了配置,并且将 json字符 反序列化 到对象 err := viper.Unmarshal(&Config) if err != nil { log.Panicf("unable to decode into struct, %v", err) } }
7.util包
-
package util import ( "encoding/json" "fmt" "github.com/golang-jwt/jwt" _ "github.com/golang-jwt/jwt" "log" "time" ) // 秘钥,如果要对接,问问上游生成的时候,有没有 秘钥 var mySigningKey = []byte("asfasfdafasdfdasfa.") //TokenHandle 解析token func TokenHandle(tokenString string) (string, error) { token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) { return mySigningKey, nil }) if err != nil { log.Println(err.Error()) return "", err } data := token.Claims.(jwt.MapClaims)["data"] var tokenData = new(TokenData) if data != nil { var info = data.(string) err = json.Unmarshal([]byte(info), &tokenData) } return tokenData.UserId, err } //CreateToken is created token func CreateToken(data TokenData) (string, error) { dataByte, err := json.Marshal(data) token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "data": string(dataByte), "exp": time.Now().Unix() + 1000*5, "iss": "ibc_business", }) tokenString, err := token.SignedString(mySigningKey) if err != nil { log.Fatal(err) return "", err } fmt.Println("加密后的token字符串", tokenString) return tokenString, nil } //TokenData is 用户对象 type TokenData struct { UserId string Age int32 NickName string Name string Phone string }
8.go.mod
module project //项目名 go 1.18 require ( github.com/33cn/chain33 v1.67.3 github.com/gin-gonic/gin v1.8.1 github.com/go-sql-driver/mysql v1.6.0 github.com/golang-jwt/jwt v3.2.1+incompatible github.com/jmoiron/sqlx v1.3.5 github.com/miguelmota/go-ethereum-hdwallet v0.1.1 github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1 github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b github.com/sirupsen/logrus v1.9.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/spf13/viper v1.13.0 ) require ( github.com/BurntSushi/toml v0.3.1 // indirect github.com/btcsuite/btcd v0.22.0-beta // indirect github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect github.com/decred/base58 v1.0.3 // indirect github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect github.com/ethereum/go-ethereum v1.10.16 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.10.0 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/goccy/go-json v0.9.7 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.11.1 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/tjfoc/gmsm v1.3.2 // indirect github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef // indirect github.com/ugorji/go/codec v1.2.7 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2 // indirect golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect google.golang.org/grpc v1.46.2 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect )
总结
以上就是 golang搭建web项目的流程,使用gin 做http请求,同时封装了标准返回,拦截token.并且使用sqlx做 mysql的连接。