服务端工程师进化史-从零开始的APP开发(4)

引言

1.开发环境搭建
2.项目环境搭建
3.golang项目基础框架-前篇
续上之前的节奏,继续框架整合,本篇将形成一个正常可运行golang web 项目。

从数据库开始,引入xorm框架,xorm封装了基本单表操作,又提供可定制化的SQL编写API。

mysql与xorm库导入

cd core
go get -u github.com/go-sql-driver/mysql
go get -u github.com/go-xorm/xorm
cd config
touch database.go

database.go

定义配置类与初始化函数

package config

import (
	"core/system"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/go-xorm/xorm"
)

type Database struct {
	Driver       string `yaml:"driver"`
	Url          string `yaml:"url"`
	Username     string `yaml:"username"`
	Password     string `yaml:"password"`
	MaxIdleConns int    `yaml:"maxIdleConns"`
	MaxOpenConns int    `yaml:"maxOpenConns"`
	ShowSQL      bool   `yaml:"showSQL"`
}

func (e *Database) Init() {
	if e == nil {
		return
	}
	engine, err := xorm.NewEngine(e.Driver, fmt.Sprintf("%s:%s@%s", e.Username, e.Password, e.Url))
	if err != nil {
		fmt.Println(err.Error())
	}
	err = engine.Ping()
	if err != nil {
		fmt.Printf("connect ping failed: %v", err)
		return
	}
	//最大空闲连接数
	engine.SetMaxIdleConns(e.MaxIdleConns)
	//最大连接数
	engine.SetMaxOpenConns(e.MaxOpenConns)
	//是否打印SQL
	engine.ShowSQL(e.ShowSQL)
	system.SetXormEngine(engine)
}

config.go 与 application.go

对应增加database配置类与xorm客户端类

package config

import "core/system"

type Config struct {
	Nacos    *Nacos    `mapstructure:""`
	Logger   *Logger   `yaml:"logger"`
	Dasebase *Database `yaml:"database"`
}

var SystemConfig = new(Config)

// 设置参数
func Setup() {
	env := system.GetEnv()
	//nacos配置
	profile := profiles[env]
	SystemConfig.Nacos = new(Nacos)
	SystemConfig.Nacos.Username = profile.Username
	SystemConfig.Nacos.Password = profile.Password
	SystemConfig.Nacos.ServerAddr = profile.ServerAddr
	SystemConfig.Nacos.Namespace = profile.Namespace
	SystemConfig.Nacos.SharedDataIds = profile.SharedDataIds
	SystemConfig.Nacos.RefreshableDataIds = profile.RefreshableDataIds
	//配置中心
	SystemConfig.Nacos.Init()
	//日志
	SystemConfig.Logger.Init()
	//数据库
	SystemConfig.Dasebase.Init()
}

package system

import (
	"github.com/go-xorm/xorm"
	"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
	"github.com/sirupsen/logrus"
)

type Application struct {
	env           string
	name          string
	iConfigClient config_client.IConfigClient
	logger        *logrus.Logger
	xormEngine    *xorm.Engine
}

func SetEnv(env string) {
	if len(application.env) == 0 {
		application.env = env
	}
}

func GetEnv() string {
	return application.env
}

func SetName(name string) {
	if len(application.name) == 0 {
		application.name = name
	}
}

func GetName() string {
	return application.name
}

func SetIConfigClient(configClient config_client.IConfigClient) {
	if application.iConfigClient == nil {
		application.iConfigClient = configClient
	}
}

func GetIConfigClient() config_client.IConfigClient {
	return application.iConfigClient
}

func SetLogger(logger *logrus.Logger) {
	if application.logger == nil {
		application.logger = logger
	}
}

func GetLogger() *logrus.Logger {
	return application.logger
}

func SetXormEngine(engine *xorm.Engine) {
	if application.xormEngine == nil {
		application.xormEngine = engine
	}
}

func GetXormEngine() *xorm.Engine {
	return application.xormEngine
}

var application = new(Application)

xorm的代码片段示例

示例中e.db代表xorm.Engine

// 新增/更新
func (e *AdminMenuDb) SaveOrUpdateAdminMenu(adminMenu *model.AdminMenu) error {
	var now = types.Time(time.Now())
	menuId := adminMenu.MenuId
	name := adminMenu.Name
	path := adminMenu.Path
	if menuId == 0 {
		count, err := e.db.Where("name = ? and path = ?", name, path).Count(&model.AdminMenu{})
		if err != nil {
			return err
		}
		if count == 0 {
			adminMenu.CreatedAt = now
			_, err := e.db.Insert(adminMenu)
			if err != nil {
				return err
			}
		}
	} else {
		adminMenu.UpdatedAt = now
		_, err := e.db.ID(menuId).Update(adminMenu)
		if err != nil {
			return err
		}
	}
	_, err := e.db.Where("name = ? and path = ?", name, path).Get(adminMenu)
	if err != nil {
		return err
	}
	return nil
}

// 指定字段更新
func (e *UserDb) UpdateUser(userId int64, avatar string, nickname string, phone string, email string, gender int8, description string) error {
	var now = types.Time(time.Now())
	_, err := e.db.ID(userId).Cols("avatar", "nickname", "phone", "email", "gender", "description").Update(&model.User{
		Avatar:      avatar,
		Nickname:    nickname,
		Phone:       phone,
		Email:       email,
		Gender:      gender,
		Description: description,
		UpdatedAt:   now,
	})
	return err
}

// 删除
func (e *AdminMenuDb) DelAdminMenu(menuId int64) error {
	_, err := e.db.ID(menuId).Delete(&model.AdminMenu{})
	if err != nil {
		return err
	}
	return nil
}

// 动态条件查询
func (e *AssetDb) GetAssetByCategoryId(assetType int8, categoryId int64, pageNum int32, pageSize int32) ([]model.Asset, error) {
	var assets []model.Asset
	session := e.db.Where("find_in_set(?, category_ids)", categoryId)
	if assetType != 0 {
		session = session.And("asset_type = ?", assetType)
	}
	err := session.Limit(int(pageSize), int((pageNum-1)*pageSize)).OrderBy("updated_at desc").Find(&assets)
	if err != nil {
		return nil, err
	}
	return assets, nil
}

// 复杂sql查询
func (e *AssetDb) GetAssetForUpgrade(limit int64) ([]model.Asset, error) {
	var assets []model.Asset
	err := e.db.SQL(
		`
		select
			a.asset_id,
			a.asset_key,
			a.asset_type,
			a.title,
			a.cover,
			a.upgrade_chapter,
			a.category_ids,
			a.author_ids,
			a.chapter_id,
			a.obj_id,
			a.created_at,
			a.updated_at 
		from asset as a 
		where 
			a.created_at between date_sub(now(), interval 7 day) and now()
		group by a.obj_id
		order by a.updated_at desc 
		limit ?
		`, limit).Find(&assets)
	if err != nil {
		return nil, err
	}
	return assets, nil
}

// 单表拼接sql查询
func (e *ComicDb) GetComicMapByIds(comicIds []int64) (map[int64]model.Comic, error) {
	if len(comicIds) == 0 {
		return nil, nil
	}
	var comics []model.Comic
	var builder strings.Builder
	var params = make([]interface{}, 0)
	builder.WriteString("comic_id in (")
	for i, length := 0, len(comicIds); i < length; i++ {
		builder.WriteString("?")
		params = append(params, comicIds[i])
		if i != length-1 {
			builder.WriteString(",")
		}
	}
	builder.WriteString(")")
	err := e.db.Where(builder.String(), params...).Find(&comics)
	if err != nil {
		return nil, err
	}
	if len(comics) == 0 {
		return nil, nil
	}
	var comicMap = make(map[int64]model.Comic, 0)
	for _, v := range comics {
		if v.ComicId != 0 {
			comicMap[v.ComicId] = v
		}
	}
	return comicMap, nil
}

// count 
func (e *AccountDb) IsExistForUsername(username string, password string) (bool, error) {
	count, err := e.db.Where("(username = ? or phone = ? or email = ?) and password = ? and status = ?", username, username, username, password, enums.ACCOUNT_STATUS_OF_ENABLE).Count(&model.Account{})
	if err != nil {
		return false, err
	}
	if count == 0 {
		return false, nil
	}
	return count == 1, nil
}

minio整合

cd core
go get -u github.com/minio/minio-go/v7
cd config
touch minio.go

创建配置类与初始化函数

minio.go

package config

import (
	"core/system"
	"core/utils"
	"fmt"

	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
)

type Minio struct {
	Endpoint        string `yaml:"endpoint"`
	AccessKeyID     string `yaml:"accessKeyId"`
	SecretAccessKey string `yaml:"secretAccessKey"`
	Secure          bool   `yaml:"secure"`
}

func (e *Minio) Init() {
	if e == nil {
		return
	}
	client, err := minio.New(e.Endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(e.AccessKeyID, e.SecretAccessKey, utils.EMPTY),
		Secure: e.Secure,
	})
	if err != nil {
		fmt.Println(err.Error())
		return
	}
	system.SetMinioClient(client)
}

config.go 与 application.go

package config

import "core/system"

type Config struct {
	Nacos    *Nacos    `mapstructure:""`
	Logger   *Logger   `yaml:"logger"`
	Dasebase *Database `yaml:"database"`
	Minio    *Minio    `yaml:"minio"`
}

var SystemConfig = new(Config)

// 设置参数
func Setup() {
	env := system.GetEnv()
	//nacos配置
	profile := profiles[env]
	SystemConfig.Nacos = new(Nacos)
	SystemConfig.Nacos.Username = profile.Username
	SystemConfig.Nacos.Password = profile.Password
	SystemConfig.Nacos.ServerAddr = profile.ServerAddr
	SystemConfig.Nacos.Namespace = profile.Namespace
	SystemConfig.Nacos.SharedDataIds = profile.SharedDataIds
	SystemConfig.Nacos.RefreshableDataIds = profile.RefreshableDataIds
	//配置中心
	SystemConfig.Nacos.Init()
	//日志
	SystemConfig.Logger.Init()
	//数据库
	SystemConfig.Dasebase.Init()
	//文件储存
	SystemConfig.Minio.Init()
}

package system

import (
	"github.com/go-xorm/xorm"
	"github.com/minio/minio-go/v7"
	"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
	"github.com/sirupsen/logrus"
)

type Application struct {
	env           string
	name          string
	iConfigClient config_client.IConfigClient
	logger        *logrus.Logger
	xormEngine    *xorm.Engine
	minioClient   *minio.Client
}

func SetEnv(env string) {
	if len(application.env) == 0 {
		application.env = env
	}
}

func GetEnv() string {
	return application.env
}

func SetName(name string) {
	if len(application.name) == 0 {
		application.name = name
	}
}

func GetName() string {
	return application.name
}

func SetIConfigClient(configClient config_client.IConfigClient) {
	if application.iConfigClient == nil {
		application.iConfigClient = configClient
	}
}

func GetIConfigClient() config_client.IConfigClient {
	return application.iConfigClient
}

func SetLogger(logger *logrus.Logger) {
	if application.logger == nil {
		application.logger = logger
	}
}

func GetLogger() *logrus.Logger {
	return application.logger
}

func SetXormEngine(engine *xorm.Engine) {
	if application.xormEngine == nil {
		application.xormEngine = engine
	}
}

func GetXormEngine() *xorm.Engine {
	return application.xormEngine
}

func SetMinioClient(client *minio.Client) {
	if application.minioClient == nil {
		application.minioClient = client
	}
}

func GetMinioClient() *minio.Client {
	return application.minioClient
}

var application = new(Application)

因对象存储存在变更情况,定义一套通用的数据结构与api。

创建常量类 storage.go

cd core
mkdir constant
cd constant
touch storage.go
package constant

const (
	MINIO = "minio"
	OSS   = "oss"
	COS   = "cos"
)

开始定义文件api结构

cd core
mkdir file
cd file
touch file_template.go
touch minio_template.go

file_template.go

package file

import (
	"core/constant"
	"core/system"
	"time"
)

type FileTemplate interface {
	CreateBucket(string) error
	ListObjects(string) ([]FileObjectInfo, error)
	PutObject(string, string, []byte) (*FileObjectInfo, error)
	PresignedGetObject(string, string, map[string]string, time.Duration) (string, error)
}

type FileObjectInfo struct {
	Name         string    `json:"name"`
	LastModified time.Time `json:"lastModified"`
	Size         int64     `json:"size"`
	Expires      time.Time `json:"expires"`
	ContentType  string    `json:"contentType"`
}

func NewFileTemplate(storage string) FileTemplate {
	var fileTemplate FileTemplate
	if storage == constant.MINIO {
		fileTemplate = NewMinioTemplate(system.GetMinioClient())
	}
	return fileTemplate
}

minio_template.go

package file

import (
	"bytes"
	"context"
	"net/url"
	"time"

	"github.com/minio/minio-go/v7"
)

type MinioTemplate struct {
	minioClient *minio.Client
}

var _ FileTemplate = &MinioTemplate{}

func NewMinioTemplate(minioClient *minio.Client) *MinioTemplate {
	return &MinioTemplate{minioClient}
}

func (e *MinioTemplate) CreateBucket(bucketName string) error {
	ok, err := e.minioClient.BucketExists(context.Background(), bucketName)
	if err != nil {
		return err
	}
	if !ok {
		err := e.minioClient.MakeBucket(context.Background(), bucketName, minio.MakeBucketOptions{})
		if err != nil {
			return err
		}
	}
	return nil
}

func (e *MinioTemplate) ListObjects(bucketName string) ([]FileObjectInfo, error) {
	objectInfos := e.minioClient.ListObjects(context.Background(), bucketName, minio.ListObjectsOptions{})
	var fileInfos = make([]FileObjectInfo, 0)
	for objectInfo := range objectInfos {
		fileInfos = append(fileInfos, FileObjectInfo{
			Name:         objectInfo.Key,
			ContentType:  objectInfo.ContentType,
			Size:         objectInfo.Size,
			LastModified: objectInfo.LastModified,
			Expires:      objectInfo.Expires,
		})
	}
	return fileInfos, nil
}

func (e *MinioTemplate) PutObject(bucketName string, objectName string, data []byte) (*FileObjectInfo, error) {
	e.CreateBucket(bucketName)
	uploadInfo, err := e.minioClient.PutObject(context.Background(), bucketName, objectName, bytes.NewBuffer(data), int64(len(data)), minio.PutObjectOptions{})
	if err != nil {
		return nil, err
	}
	return &FileObjectInfo{
		Name:         uploadInfo.Key,
		Size:         uploadInfo.Size,
		LastModified: uploadInfo.LastModified,
		Expires:      uploadInfo.Expiration,
	}, nil
}

func (e *MinioTemplate) PresignedGetObject(bucketName string, objectName string, headers map[string]string, expires time.Duration) (string, error) {
	var reqParams = make(url.Values)
	for k, v := range headers {
		reqParams.Set(k, v)
	}
	presignedURL, err := e.minioClient.PresignedGetObject(context.Background(), bucketName, objectName, expires, reqParams)
	if err != nil {
		return "", err
	}
	return presignedURL.RequestURI(), err
}

minio使用代码示例

// 请求
func (e *FileItemHandler) UploadFile(wc *web.WebContext) interface{} {
	var bucketName = wc.Param("bucketName")
	if len(bucketName) == 0 {
		return response.Success()
	}
	file, header, err := wc.FormFile("file")
	if err != nil {
		return response.Fail(err.Error())
	}
	defer file.Close()
	data, err := io.ReadAll(file)
	if err != nil {
		return response.Fail(err.Error())
	}
	fileName := header.Filename
	fileInfo, err := e.fileTemplate.PutObject(bucketName, fileName, data)
	if err != nil {
		wc.AbortWithError(err)
	}
	return response.ReturnOK(fileInfo)
}

web框架整合

web框架采用gin,但笔者原先为了处理requestId传递问题,为了处理requestId传递问题,给gin套了层壳(不知道对还是错?)

cd core
go get -u github.com/gin-gonic/gin
cd config
touch server.go

server.go

package config

import (
	"core/system"

	"github.com/gin-gonic/gin"
)

type Server struct {
	Port int `yaml:"port"`
}

func (e *Server) Init() {
	if e == nil {
		return
	}
	engine := gin.New()
	engine.SetTrustedProxies([]string{"127.0.0.1"})
	system.SetGinEngine(engine)
}

config.go 与 application.go

package config

import "core/system"

type Config struct {
	Nacos    *Nacos    `mapstructure:""`
	Logger   *Logger   `yaml:"logger"`
	Dasebase *Database `yaml:"database"`
	Minio    *Minio    `yaml:"minio"`
	Server   *Server   `yaml:"server"`
}

var SystemConfig = new(Config)

// 设置参数
func Setup() {
	env := system.GetEnv()
	//nacos配置
	profile := profiles[env]
	SystemConfig.Nacos = new(Nacos)
	SystemConfig.Nacos.Username = profile.Username
	SystemConfig.Nacos.Password = profile.Password
	SystemConfig.Nacos.ServerAddr = profile.ServerAddr
	SystemConfig.Nacos.Namespace = profile.Namespace
	SystemConfig.Nacos.SharedDataIds = profile.SharedDataIds
	SystemConfig.Nacos.RefreshableDataIds = profile.RefreshableDataIds
	//配置中心
	SystemConfig.Nacos.Init()
	//日志
	SystemConfig.Logger.Init()
	//数据库
	SystemConfig.Dasebase.Init()
	//文件储存
	SystemConfig.Minio.Init()
	//http
	SystemConfig.Server.Init()
}

package system

import (
	"github.com/gin-gonic/gin"
	"github.com/go-xorm/xorm"
	"github.com/minio/minio-go/v7"
	"github.com/nacos-group/nacos-sdk-go/v2/clients/config_client"
	"github.com/sirupsen/logrus"
)

type Application struct {
	env           string
	name          string
	iConfigClient config_client.IConfigClient
	logger        *logrus.Logger
	xormEngine    *xorm.Engine
	minioClient   *minio.Client
	ginEngine     *gin.Engine
}

func SetEnv(env string) {
	if len(application.env) == 0 {
		application.env = env
	}
}

func GetEnv() string {
	return application.env
}

func SetName(name string) {
	if len(application.name) == 0 {
		application.name = name
	}
}

func GetName() string {
	return application.name
}

func SetIConfigClient(configClient config_client.IConfigClient) {
	if application.iConfigClient == nil {
		application.iConfigClient = configClient
	}
}

func GetIConfigClient() config_client.IConfigClient {
	return application.iConfigClient
}

func SetLogger(logger *logrus.Logger) {
	if application.logger == nil {
		application.logger = logger
	}
}

func GetLogger() *logrus.Logger {
	return application.logger
}

func SetXormEngine(engine *xorm.Engine) {
	if application.xormEngine == nil {
		application.xormEngine = engine
	}
}

func GetXormEngine() *xorm.Engine {
	return application.xormEngine
}

func SetMinioClient(client *minio.Client) {
	if application.minioClient == nil {
		application.minioClient = client
	}
}

func GetMinioClient() *minio.Client {
	return application.minioClient
}

func SetGinEngine(engine *gin.Engine) {
	if application.ginEngine == nil {
		application.ginEngine = engine
	}
}

func GetGinEngine() *gin.Engine {
	return application.ginEngine
}

var application = new(Application)

gin的api套壳

定义常量类

cd core/constant
touch logger.go
touch http_header.go

logger.go

package constant

const (
	REQUEST_ID string = "requestId"
	DB_ERROR   string = "db_error"
)

http_header.go

package constant

const (
	AUTHORIZATION = "Authorization"
	USER_AGENT    = "User-Agent"
	X_TIMESTAMP   = "X-Timestamp"
	X_REQUEST_ID  = "X-Request-Id"
	X_USER_ID     = "X-User-Id"
)
cd core
mkdir response
touch response/response.go

mkdir web
cd web
touch context.go
touch group.go
touch web.go

response.go

package response

import (
	"net/http"
	"time"
)

type Response struct {
	Code      int         `json:"code"`           // 响应编码
	Message   string      `json:"message"`        // 响应信息
	Data      interface{} `json:"data,omitempty"` // 响应体
	Timestamp int64       `json:"timestamp"`      // 当前时间戳
}

// constructor
func NewResponse(code int, message string, data interface{}) *Response {
	var response = new(Response)
	response.Code = code
	response.Message = message
	response.Data = data
	response.Timestamp = time.Now().UnixMilli()
	return response
}

// success
func Success() *Response {
	return ReturnOK(nil)
}

// fail
func Fail(message string) *Response {
	code := http.StatusInternalServerError
	if len(message) == 0 {
		message = http.StatusText(code)
	}
	return NewResponse(code, message, nil)
}

// return ok
func ReturnOK(data interface{}) *Response {
	code := http.StatusOK
	message := http.StatusText(code)
	return NewResponse(code, message, data)
}

// return error
func ReturnError(code int, message string) *Response {
	return NewResponse(code, message, nil)
}

context.go

package web

import (
	"context"
	"core/constant"
	"core/system"
	"mime/multipart"
	"net/http"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/sirupsen/logrus"
)

type WebContext struct {
	context *gin.Context
}

func NewWebContext(context *gin.Context) *WebContext {
	return &WebContext{context}
}

func (e *WebContext) Trace(format string, args ...interface{}) {
	system.GetLogger().WithFields(logrus.Fields{
		constant.REQUEST_ID: e.context.Request.Header.Get(constant.X_REQUEST_ID),
	}).Tracef(format, args...)
}

func (e *WebContext) Debug(format string, args ...interface{}) {
	system.GetLogger().WithFields(logrus.Fields{
		constant.REQUEST_ID: e.context.Request.Header.Get(constant.X_REQUEST_ID),
	}).Debugf(format, args...)
}

func (e *WebContext) Info(format string, args ...interface{}) {
	system.GetLogger().WithFields(logrus.Fields{
		constant.REQUEST_ID: e.context.Request.Header.Get(constant.X_REQUEST_ID),
	}).Infof(format, args...)
}

func (e *WebContext) Warn(format string, args ...interface{}) {
	system.GetLogger().WithFields(logrus.Fields{
		constant.REQUEST_ID: e.context.Request.Header.Get(constant.X_REQUEST_ID),
	}).Warnf(format, args...)
}

func (e *WebContext) Error(format string, args ...interface{}) {
	system.GetLogger().WithFields(logrus.Fields{
		constant.REQUEST_ID: e.context.Request.Header.Get(constant.X_REQUEST_ID),
	}).Errorf(format, args...)
}

func (ctx *WebContext) Fatal(format string, args ...interface{}) {
	system.GetLogger().WithFields(logrus.Fields{
		constant.REQUEST_ID: ctx.context.Request.Header.Get(constant.X_REQUEST_ID),
	}).Fatalf(format, args...)
}

func (e *WebContext) Panic(format string, args ...interface{}) {
	system.GetLogger().WithFields(logrus.Fields{
		constant.REQUEST_ID: e.context.Request.Header.Get(constant.X_REQUEST_ID),
	}).Panicf(format, args...)
}

func (e *WebContext) Background() context.Context {
	return e.context.Request.Context()
}

func (e *WebContext) AbortWithError(err error) {
	e.Error(err.Error())
	e.context.AbortWithError(http.StatusInternalServerError, err)
}

func (e *WebContext) ClientIP() string {
	return e.context.ClientIP()
}

func (e *WebContext) GetHeader(key string) string {
	return e.context.Request.Header.Get(key)
}

func (e *WebContext) PostForm(key string) string {
	return e.context.PostForm(key)
}

func (e *WebContext) Param(key string) string {
	return e.context.Param(key)
}

func (e *WebContext) Query(key string) string {
	return e.context.Query(key)
}

func (e *WebContext) DefaultQuery(key string, defaultValue string) string {
	return e.context.DefaultQuery(key, defaultValue)
}

func (e *WebContext) ShouldBindJSON(obj any) error {
	return e.context.ShouldBindJSON(obj)
}

func (e *WebContext) BindJSON(obj any) error {
	return e.context.BindJSON(obj)
}

func (e *WebContext) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
	return e.context.Request.FormFile(key)
}

func (e *WebContext) MultipartForm() (*multipart.Form, error) {
	return e.context.MultipartForm()
}

func GetUserId(wc *WebContext) int64 {
	userIdStr := wc.GetHeader(constant.X_USER_ID)
	if len(userIdStr) == 0 {
		return 0
	}
	userId, err := strconv.ParseInt(userIdStr, 10, 64)
	if err != nil {
		wc.Error(err.Error())
		return 0
	}
	return userId
}

group.go

package web

import (
	"core/system"
	"net/http"

	"github.com/gin-gonic/gin"
)

type WebHandlerFunc func(*WebContext) interface{}

type IWebRoutes interface {
	GET(string, ...WebHandlerFunc) IWebRoutes
	POST(string, ...WebHandlerFunc) IWebRoutes
	DELETE(string, ...WebHandlerFunc) IWebRoutes
	PATCH(string, ...WebHandlerFunc) IWebRoutes
	PUT(string, ...WebHandlerFunc) IWebRoutes
	OPTIONS(string, ...WebHandlerFunc) IWebRoutes
	HEAD(string, ...WebHandlerFunc) IWebRoutes
}

type IWebController interface {
	Router(iWebRoutes IWebRoutes)
}

type WebRouterGroup struct {
	group   *gin.RouterGroup
	iRoutes gin.IRoutes
}

var _ IWebRoutes = &WebRouterGroup{}

func (e *WebRouterGroup) buildHandlersChain(handlers []WebHandlerFunc) []gin.HandlerFunc {
	var handlersChain = make([]gin.HandlerFunc, 0)
	if len(handlers) > 0 {
		for _, h := range handlers {
			handlersChain = append(handlersChain, func(ctx *gin.Context) {
				var res = h(NewWebContext(ctx))
				if len(ctx.Errors) == 0 && res != nil {
					ctx.JSON(http.StatusOK, res)
				}
			})
		}
	}
	return handlersChain
}

func (e *WebRouterGroup) Use(handlers ...gin.HandlerFunc) IWebRoutes {
	system.GetGinEngine().RouterGroup.Use(handlers...)
	return e
}

func (e *WebRouterGroup) Group(relativePath string, handlers ...WebHandlerFunc) IWebRoutes {
	return &WebRouterGroup{
		group:   e.group,
		iRoutes: e.group.Group(relativePath, e.buildHandlersChain(handlers)...),
	}
}

func (e *WebRouterGroup) POST(relativePath string, handlers ...WebHandlerFunc) IWebRoutes {
	return &WebRouterGroup{
		group:   e.group,
		iRoutes: e.iRoutes.POST(relativePath, e.buildHandlersChain(handlers)...),
	}
}

func (e *WebRouterGroup) GET(relativePath string, handlers ...WebHandlerFunc) IWebRoutes {
	return &WebRouterGroup{
		group:   e.group,
		iRoutes: e.iRoutes.GET(relativePath, e.buildHandlersChain(handlers)...),
	}
}

func (e *WebRouterGroup) DELETE(relativePath string, handlers ...WebHandlerFunc) IWebRoutes {
	return &WebRouterGroup{
		group:   e.group,
		iRoutes: e.iRoutes.DELETE(relativePath, e.buildHandlersChain(handlers)...),
	}
}

func (e *WebRouterGroup) PATCH(relativePath string, handlers ...WebHandlerFunc) IWebRoutes {
	return &WebRouterGroup{
		group:   e.group,
		iRoutes: e.iRoutes.PATCH(relativePath, e.buildHandlersChain(handlers)...),
	}
}

func (e *WebRouterGroup) PUT(relativePath string, handlers ...WebHandlerFunc) IWebRoutes {
	return &WebRouterGroup{
		group:   e.group,
		iRoutes: e.iRoutes.PUT(relativePath, e.buildHandlersChain(handlers)...),
	}
}

func (e *WebRouterGroup) OPTIONS(relativePath string, handlers ...WebHandlerFunc) IWebRoutes {
	return &WebRouterGroup{
		group:   e.group,
		iRoutes: e.iRoutes.OPTIONS(relativePath, e.buildHandlersChain(handlers)...),
	}
}

func (e *WebRouterGroup) HEAD(relativePath string, handlers ...WebHandlerFunc) IWebRoutes {
	return &WebRouterGroup{
		group:   e.group,
		iRoutes: e.iRoutes.HEAD(relativePath, e.buildHandlersChain(handlers)...),
	}
}

web.go

package web

import (
	"github.com/gin-gonic/gin"
)

var (
	routers = make([]func(*WebRouterGroup), 0)
)

// 添加路由
func AddRouter(handlers ...func(*WebRouterGroup)) {
	routers = append(routers, handlers...)
}

// 初始化路由
func InitRouter(engine *gin.Engine) {
	var wrg = new(WebRouterGroup)
	wrg.group = &engine.RouterGroup
	for _, h := range routers {
		h(wrg)
	}
}

gin使用代码示例

因笔者封装一层,写法略微不同

cd admin
mkdir router
mkdir controller
mkdir handler
touch router.go
touch hello_world_handler.go
touch hello_world_controller.go

hello_world_handler.go

业务处理层

package handler

import (
	"core/response"
	"core/web"
)

type HelloWorldHandler struct {
}

func (e *HelloWorldHandler) Hello(wc *web.WebContext) interface{} {
	return response.ReturnOK("hello,world!")
}

hello_world_controller.go

接口层

package controller

import (
	"admin/handler"
	"core/web"
)

type HelloWorldController struct {
}

var _ web.IWebController = &HelloWorldController{}

func (e *HelloWorldController) Router(iWebRoutes web.IWebRoutes) {
	var helloWorldHandler = handler.HelloWorldHandler{}
	iWebRoutes.GET("/hello/world", helloWorldHandler.Hello)
}

router.go

接口路由层,通过golang的init方法自动加入到全局的list里

package router

import (
	"admin/controller"
	"core/web"
)

func init() {
	//添加路由
	web.AddRouter(func(wrg *web.WebRouterGroup) {
		r := wrg.Group("/admin")
		{
			new(controller.HelloWorldController).Router(r)
		}
	})
}

因整套gin的api都经过封装,若有扩展,则需根据自身的需求,在web目录下增加。

在cmd.go增加web启动函数

添加startServer函数

package cmd

import (
	"core/config"
	"core/system"
	"core/web"
	"errors"
	"fmt"
	"os"
	"strconv"

	"github.com/spf13/cobra"
)

var (
	env, name string
	rootCmd   = &cobra.Command{
		Use:          system.GetName(),
		Short:        system.GetName(),
		SilenceUsage: true,
		Long:         system.GetName(),
		Args: func(cmd *cobra.Command, args []string) error {
			if len(args) < 1 {
				return errors.New("requires at least one arg")
			}
			return nil
		},
	}
	StartCmd = &cobra.Command{
		Use:     "start",
		Short:   "Start application",
		Example: system.GetName() + " start -e dev",
		Run: func(cmd *cobra.Command, args []string) {
			run()
		},
	}
)

func init() {
	rootCmd.AddCommand(StartCmd)
	StartCmd.PersistentFlags().StringVarP(&env, "start", "e", "dev", "Setting up the running environment")
}

func run() {
	setupConfig()
	startServer()
}

func setupConfig() {
	fmt.Printf("The profile active is %s\n", env)
	system.SetEnv(env)
	if len(name) > 0 {
		system.SetName(name)
	}
	config.Setup()
}

func startServer() {
	server := config.SystemConfig.Server
	if server == nil {
		return
	}
	fmt.Println("Start server...")
	engine := system.GetGinEngine()
	//初始化
	web.InitRouter(engine)
	//端口
	port := strconv.Itoa(server.Port)
	if len(port) > 0 {
		os.Setenv("PORT", port)
	}
	engine.Run()
}

func Execute(applicationName string) {
	system.SetName(applicationName)
	if err := rootCmd.Execute(); err != nil {
		fmt.Printf("Execute err : %s\n", err.Error())
		os.Exit(-1)
	}
}

项目结构

.
├── LICENSE
├── Makefile
├── README.md
├── admin
│   ├── controller
│   │   └── hello_world_controller.go
│   ├── go.mod
│   ├── handler
│   │   └── hello_world_handler.go
│   ├── main.go
│   └── router
│       └── router.go
├── basic
│   └── go.mod
├── business
│   └── go.mod
├── core
│   ├── cmd
│   │   └── cmd.go
│   ├── config
│   │   ├── config.go
│   │   ├── database.go
│   │   ├── logger.go
│   │   ├── minio.go
│   │   ├── nacos.go
│   │   └── server.go
│   ├── constant
│   │   ├── http_header.go
│   │   ├── logger.go
│   │   └── storage.go
│   ├── file
│   │   ├── file_template.go
│   │   └── minio_template.go
│   ├── go.mod
│   ├── go.sum
│   ├── response
│   │   └── response.go
│   ├── system
│   │   └── application.go
│   ├── utils
│   │   ├── date.go
│   │   └── string.go
│   └── web
│       ├── context.go
│       ├── group.go
│       └── web.go
├── go.work
├── go.work.sum
└── oauth
    └── go.mod

总结

综合前面几篇文章,基础框架已搭建完成,可形成正常的api回路,一个正常、可以跑起来的golang项目。
当前项目:

https://github.com/liaz-repo/liaz-server.git

照旧附上app的gitee地址(漫画/轻小说app):

https://gitee.com/liaz-app/liaz-android/releases/download/1.0.0/app-arm64-v8a-release.apk
  • 7
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值