目录
例如:习惯了java springboot 开发方式,比较疑惑golang web开发的流程和模块化的区分,
就golang web整个后台模块做下梳理。
golang web是指 web后台HTTP接口开发模块。
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 启动入口
相信大家看完前面的包,也有很多疑惑,该怎么开始呢?
现在就一个包,一个包,把代码拆开来
import (
"fmt"
"app"
"log"
)
func main() {
//调用 app包里面的启动
err := app.Run()
if err != nil {
log.Fatal(err)
}
fmt.Println("go web start ..!")
}
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包
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的连接。