herman

Herman专注于后端快速上手的一款开源,简单,轻量框架。

MIT License

Stars
89
Committers
2

Herman

1.

HermanGinCasbinKafkaMysqlRedisZapCobraGrom

application ------------------------------------------------- 
  constants ------------------------------------------------ 
  controllers ---------------------------------------------- 
  models --------------------------------------------------- 
  repositories --------------------------------------------- 
  services ------------------------------------------------- 
  validates ------------------------------------------------ 
  request.go ----------------------------------------------- 
  response.go ---------------------------------------------- 
cmd --------------------------------------------------------- 
config ------------------------------------------------------ 
database ---------------------------------------------------- 
  migrations ----------------------------------------------- 
  seeders -------------------------------------------------- 
jobs -------------------------------------------------------- 
kernel ------------------------------------------------------ 
  app ------------------------------------------------------ 
  casbin --------------------------------------------------- Casbin
  cobra ---------------------------------------------------- 
  core ----------------------------------------------------- 
  kafka ---------------------------------------------------- kafka
  log ------------------------------------------------------ 
  mysql ---------------------------------------------------- Mysql
  redis ---------------------------------------------------- Redis
  servers -------------------------------------------------- 
runtime ----------------------------------------------------- 
  logs ----------------------------------------------------- 
storages ---------------------------------------------------- 
tests ------------------------------------------------------- 
.air.toml --------------------------------------------------- Air
.gitignore -------------------------------------------------- gitignore
go.mod ------------------------------------------------------ go.mod
go.sum ------------------------------------------------------ go.sum
config.yaml.debug ------------------------------------------- 
config.yaml.test -------------------------------------------- 
config.yaml.release ----------------------------------------- 
Dockerfile -------------------------------------------------- Dodcker
docker-compose.yaml ----------------------------------------- Dodcker
LICENSE ----------------------------------------------------- 
Makefile ---------------------------------------------------- Makefile
main.go ----------------------------------------------------- 
README.md --------------------------------------------------- Readme

1

  • .gouser``user_login
  • 1_init.down.sql``1_init.up.sql1initdownup
  • (CSSJS)CSStest.css``test_user.css

2

  • json

    type Users struct {
       Id           uint       `json:"id" gorm:"primary_key"`
       User         string     `json:"user"`
       Password     string     `json:"password"`
       Nickname     string     `json:"nickname"`
       Sex          string     `json:"sex"`
       Age          int        `json:"age"`
       Region       string     `json:"region"`
       Phone        string     `json:"phone"`
       Email        string     `json:"email"`
       Introduction string     `json:"introduction"`
       Status       string     `json:"status"`
       CreatedAt    time.Time  `json:"createdAt"`
       UpdatedAt    time.Time  `json:"updatedAt"`
       DeletedAt    *time.Time `json:"deletedAt" sql:"index"`
    }
    

3

  • Success``TokenNotExit

4

  • user``user_role
  • user_id``user_name
  • pk_ uk_ idx_pk_user_id``uk_user_name``idx_user_age
  • pk+pk_mainfk++fk_sub_main

1

3config.yaml.debug``config.yaml.test``config.yaml.release``config.yaml.debug``config.yaml

cp config.yaml.debug config.yaml

2MySQLRedis

MysqlRedisMySQLRedis

# 
mysql:
  # IP
  host: 127.0.0.1
  # 
  port: 3306
  # 
  user: root
  # 
  password: root
  # 
  dbname: herman
  # 
  max_open_conn: 100
  # max_open_conn
  max_idle_conn: 10

# Redis
redis:
  # IP
  host: 127.0.0.1
  # 
  port: 6380
  # 
  username:
  # 
  password:
  # 0
  db: 0
  # 
  pool_size: 100

3

Go

go mod download

4

1

go build -o herman . # herman
herman server --host=0.0.0.0 --port=8000 --migrate=true # hostportmigtate

2

go run main.go server --host=0.0.0.0 --port=8000 --migrate=true # hostport

3

****Go 1.16

go install github.com/cosmtrek/air@latest # 

.air.toml

air init
# [Air](https://github.com/cosmtrek/air) TOML 

# 
#  .  `tmp_dir`  `root` 
root = "."
tmp_dir = "tmp"

[build]
# shell `make`
cmd = "go build -o ./tmp/herman.exe ."
# `cmd`
bin = "tmp\\herman.exe server"
# .
include_ext = ["go", "tpl", "tmpl", "html"]
# 
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
# 
include_dir = []
# 
exclude_file = []
# 
delay = 1000 # ms
# 
stop_on_error = true
# air`tmp_dir`
log = "air_errors.log"

[log]
# 
time = true

[color]
# 
main = "magenta"
watcher = "cyan"
build = "yellow"
runner = "green"

[misc]
# tmp
clean_on_exit = true
air

2.

HermanHTTP

  • Golangmain.goHermanmain.go
  • cobrainitJWT
  • Gin
  • GinHTTP

GolangHermanRedisMySQLCasbin/kernel/core/Container.go

package core

import (
	"github.com/casbin/casbin/v2"
	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v8"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

var (
	Engine *gin.Engine
	Log    *zap.SugaredLogger
	Db     *gorm.DB
	Redis  *redis.Client
	Casbin *casbin.CachedEnforcer
)

********/middlewares

// ServerHandler 
// @return gin.HandlerFunc
func ServerHandler() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		Reload() // 
		ctx.Next()
		Close() // 
	}
}

GinAPI

func NewServer(host string, port uint) {
	// gin
	gin.SetMode(app.Config.Mode)
	// gin
	engine := gin.New()
	// 
	engine.Use(log.GinLogger()).Use(middlewares.CatchError()).Use(middlewares.ServerHandler())
	// 
	core.Engine = routers.InitRouter(engine)
	// 
	Run(host, port)
}
// Jwt 
// @return gin.HandlerFunc 
func Jwt(guard string) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		if VerifyRoute(ctx.Request.URL.Path, ctx.Request.Method, MiddlewareConstant.ExcludeRoute) {
			return
		}
		claims := utils.JwtVerify(ctx, guard)
		switch guard {
		case "user", "mobile": // 
			// 
			ctx.Set("user", repositories.User().GetUserInfo(claims.Uid))
		case "admin": // 
			ctx.Set("admin", repositories.Admin().GetAdminInfo(claims.Uid))
		case "merchant": // 

		default:
			panic(MiddlewareConstant.GuardError)
		}
		ctx.Next()
	}
}
// 
adminRouter := api.Group("/admin", middlewares.Jwt("admin"), middlewares.CheckPermission())
{
    admin.Router(adminRouter)
}

cobracmd``/kernel/cobra/cobra.go

// HermanVersionCmd herman
var (
	HermanVersionCmd = &cobra.Command{
		Use:          "version",
		Short:        "Get herman version",
		Example:      "herman version",
		SilenceUsage: true,
		RunE: func(cmd *cobra.Command, args []string) error {
			fmt.Printf(`Herman version: %v`, color.GreenString(app.Version))
			return nil
		},
	}
)
// rootCmd 
var rootCmd = &cobra.Command{Use: "herman"}

// 
func init() {
	// 
	cobra.OnInitialize(app.InitConfig, servers.ZapLogs, func() {
		if command.IsMigrate {
			// 
			_ = command.Migrate("up")
		}
		// 
		if !command.MigrationStatus {
			middlewares.Reload()
		}
	})

	// 
	rootCmd.AddCommand(command.HermanVersionCmd)
}

1

herman version # Herman version: 1.0.0

2

herman migrate --direction=up --number=1 # 1
// init 
// @return void
func init() {
	// 
	MigrationCmd.Flags().BoolVarP(&MigrationStatus, "status", "s", false, "Database migration status")
	// updown
	MigrationCmd.Flags().StringVarP(&direction, "direction", "d", "up", "Database migration")
	// Error: Dirty database version XX.
	MigrationCmd.Flags().UintVarP(&version, "version", "v", 0, "Database version")
	// 1herman -d down -n 1
	MigrationCmd.Flags().UintVarP(&number, "number", "n", 0, "Database migration steps")
}

3JWT

herman jwt:secret

4

herman server --host=0.0.0.0 --port=8000 --migrate=true # 
herman server # 8000

cobrahttps://cobra.dev/

kafkajobs

// SendSms 
// @param string topic 
// @return void
func SendSms(topic string) {
	var data map[string]interface{}
	// 
	kafkaConsumer := ExecConsumer(topic)
	for {
		// 
		message := <-kafkaConsumer.MessageQueue
		// JSONmap
		if err := json.Unmarshal(message, &data); err != nil {
			app.Log.Errorf("Consumer sms json data failed, err:%v", err)
		}
		execSend(data)
	}
}
jobs.Dispatch(data,jobs.SendSms)
	for {
		// 
		message := <-kafkaConsumer.MessageQueue
		// JSONmap
		if err := json.Unmarshal(message, &data); err != nil {
			app.Log.Errorf("Consumer sms json data failed, err:%v", err)
		}
		execSend(data)
	}

Dispatchdatatimetime

var data map[string]interface{}
data["topic"] = "sms_send"
data["time"] = time.Now().Add(time.Second * 60) // 60
jobs.Dispatch(data,jobs.SendSms)

Redis/kernel/core/container.go

// 
ctx := context.Background()

key

val, err := core.Redis.Set(ctx, "key", 1)
fmt.Println(val)

key

get := core.Redis.Get(ctx, "key").Result()
fmt.Println(get.Val(), get.Err())

key

core.Redis.Set(ctx, "key", 1, time.Minute*30)

Redishttps://redis.uptrace.dev/zh/guide/

ZapHerman

// 
app.Log.info(data)
// 
app.Log.infoln(data)
// 
app.Log.Debug(data)
// 
app.Log.Error(data)
// 
app.Log.Errorln(data)
// 
app.Log.Fatal(data)

APIhttps://pkg.go.dev/go.uber.org/zap

/app/utils

// Factory 
// @return factory 
func Factory() (factory *CaptchaService.CaptchaServiceFactory) { // 
	// 
	factory = CaptchaService.NewCaptchaServiceFactory(
		CaptchaConfig.BuildConfig(app.Config.Captcha.CacheType,
			app.Config.Captcha.ResourcePath,
			&CaptchaConfig.WatermarkConfig{
				Text: app.Config.Captcha.Text,
			},
			nil, nil, app.Config.Captcha.CacheExpireSec))
	// 
	factory.RegisterCache(Constant.MemCacheKey, CaptchaService.NewMemCacheService(CaptchaConstant.CacheMaxNumber))
	// redis
	factory.RegisterCache(Constant.RedisCacheKey, CaptchaService.NewConfigRedisCacheService([]string{fmt.Sprintf("%s:%d",
		app.Config.Redis.Host,
		app.Config.Redis.Port,
	)},
		app.Config.Redis.UserName,
		app.Config.Redis.Password,
		false,
		app.Config.Redis.Db,
	))
	// 
	factory.RegisterService(Constant.ClickWordCaptcha, CaptchaService.NewClickWordCaptchaService(factory))
	// 
	factory.RegisterService(Constant.BlockPuzzleCaptcha, CaptchaService.NewBlockPuzzleCaptchaService(factory))

	return factory
}

CasbinRBAC, ABACACLRBACGORM/kernel/casbin/casbin.goCasbin/kernel/core/container.go

success, _ := core.Casbin.Enforce(info.User, ctx.Request.URL.Path, ctx.Request.Method)

https://casbin.org/zh/docs/category/the-basics

config.yaml``config

app.Config

MySQL

app.Config.Mysql

config.yaml

viper.Get("app")

3.

Gin/routers/router.go

func InitRouter(rootEngine *gin.Engine) *gin.Engine {
	// 
	rootEngine.GET("/", func(context *gin.Context) {
		response := app.Request{Context: context}
		response.Success(app.D(map[string]interface{}{
			"message": "Welcome to Herman!",
		}))
	})
	// 
	api := rootEngine.Group(app.Config.AppPrefix)
	// 
	api.GET("/captcha", CaptchaController.GetCaptcha)
	// 
	api.POST("/captcha/check", CaptchaController.CheckCaptcha)

	// 
	userRouter := api.Group("/user", middlewares.Jwt("user"))
	{
		mobile.Router(userRouter)
	}

	// 
	adminRouter := api.Group("/admin", middlewares.Jwt("admin"), middlewares.CheckPermission())
	{
		admin.Router(adminRouter)
	}

	return rootEngine
}

4.


// AddAdmin 
// @param *gin.Context ctx 
// @return void
func AddAdmin(ctx *gin.Context) {
	context := app.Request{Context: ctx} // 
	data := context.Params() // 
	AdminService.Add(AdminValidate.Add.Check(data)) // 
	context.Json(nil) // 
}

5.

// Add 
var Add = validates.Validates{Validate: AddValidate{}}

// AddValidate 
type AddValidate struct {
	User         string       `json:"user" validate:"required,min=5,max=15" label:""`
	Password     string       `json:"password" validate:"required,min=6,max=15" label:""`
	Roles        []role.Roles `json:"roles" validate:"required" label:""`
	Photo        string       `json:"photo" validate:"omitempty,url,max=255" label:""`
	Name         string       `json:"name" validate:"omitempty,max=20" label:""`
	Card         string       `json:"card" validate:"omitempty,max=20" label:""`
	Sex          uint8        `json:"sex" validate:"required,oneof=1 2 3" label:""`
	Age          uint8        `json:"age" validate:"required,min=0,max=120" label:""`
	Region       string       `json:"region" validate:"omitempty,max=255" label:""`
	Phone        string       `json:"phone" validate:"omitempty,len=11" label:""`
	Email        string       `json:"email" validate:"omitempty,email" label:""`
	Introduction string       `json:"introduction" validate:"omitempty" label:""`
	State        uint8        `json:"state" validate:"required,oneof=1 2" label:""`
	Sort         uint         `json:"sort" validate:"omitempty" label:""`
}

check

// Check 
// @param map[string]interface{} data 
// @return void
func (base Validates) Check(data map[string]interface{}) (toMap map[string]interface{}) {
	// map
	if err := mapstructure.WeakDecode(data, &base.Validate); err != nil {
		panic(constants.MapToStruct)
	}
	if err := Validate(base.Validate); err != nil {
		panic(err.Error())
	}

	toMap, err := utils.ToMap(base.Validate, "json")

	if err != nil {
		panic(constants.StructToMap)
	}
	return toMap
}
// Login 
// @param *gin.Context ctx 
// @return void
func Login(ctx *gin.Context) {
	context := app.Request{Context: ctx}
	data := context.Params()
	context.Json(AdminService.Login(AdminValidate.Login(data)), AdminConstant.LoginSuccess)
}

// CaptchaLoginValidate 
type CaptchaLoginValidate struct {
	User        string `json:"user" validate:"required,min=5,max=15" label:""`
	Password    string `json:"password" validate:"required,min=6,max=15" label:""`
	CaptchaType int    `json:"captchaType" validate:"required,numeric,oneof=1 2" label:""`
	Token       string `json:"token" validate:"required" label:"Token"`
	PointJson   string `json:"pointJson" validate:"required" label:"PointJson"`
}

// ExcludeCaptchaLoginValidate 
type ExcludeCaptchaLoginValidate struct {
	User     string `json:"user" validate:"required,min=5,max=15" label:""`
	Password string `json:"password" validate:"required,min=6,max=15" label:""`
}

// Login 
// @param map[string]interface{} data 
// @return toMap 
func Login(data map[string]interface{}) (toMap map[string]interface{}) {
	// 
	if !app.Config.Captcha.Switch {
		return excludeCaptchaLogin(data)
	}
	return captchaLogin(data)
}

// captchaLogin 
// @param map[string]interface{} data 
// @return toMap 
func captchaLogin(data map[string]interface{}) (toMap map[string]interface{}) {
	var login CaptchaLoginValidate
	// map
	if err := mapstructure.WeakDecode(data, &login); err != nil {
		panic(constants.MapToStruct)
	}

	if err := validates.Validate(login); err != nil {
		panic(err.Error())
	}

	// 
	err := utils.Factory().GetService(fmt.Sprintf("%s", data["captchaType"])).Verification(fmt.Sprintf("%s", data["token"]),
		fmt.Sprintf("%s", data["PointJson"]))
	if err != nil {
		panic(CaptchaConstant.CheckCaptchaError)
	}

	toMap, err = utils.ToMap(&login, "json")
	if err != nil {
		panic(constants.StructToMap)
	}

	return toMap
}

// excludeCaptchaLogin 
// @param map[string]interface{} data 
// @return toMap 
func excludeCaptchaLogin(data map[string]interface{}) (toMap map[string]interface{}) {
	var login ExcludeCaptchaLoginValidate
	// map
	if err := mapstructure.WeakDecode(data, &login); err != nil {
		panic(constants.MapToStruct)
	}

	if err := validates.Validate(login); err != nil {
		panic(err.Error())
	}

	toMap, err := utils.ToMap(&login, "json")
	if err != nil {
		panic(constants.StructToMap)
	}

	return toMap
}

2

6.

err := core.Db().Transaction(func(tx *gorm.DB) error {
   // casbin
   _, _ = casbin.InitEnforcer(casbin.GetAdminPolicy(), tx)
   // Key
   if isExist, _ := repositories.Role(tx).KeyIsExist(data["role"].(string)); isExist {
      return errors.New(RoleConstant.KeyExist)
   }
   roles := data["roles"]
   rules := data["rules"]
   delete(data, "roles")
   delete(data, "rules")
   // 
   roleInfo, err := repositories.Role(tx).Insert(data)
   if err != nil {
      return errors.New(RoleConstant.AddFail)
   }
   // 
   if err := AddPolicies(roles.([]role.Roles), rules.([]role.Rules), roleInfo); err != nil {
      return err
   }
   return nil
})

CasbinCasbinDb

_, _ = casbin.InitEnforcer(casbin.GetAdminPolicy(), tx)
roleInfo, err := repositories.Role(tx).Insert(data)
if err != nil {
    return errors.New(RoleConstant.AddFail)
}

7.

ServiceModelModel****************************

package repositories

import (
	"github.com/herman-hang/herman/app/constants"
	"github.com/herman-hang/herman/app/utils"
	"github.com/mitchellh/mapstructure"
	"gorm.io/gorm"
)

// BaseRepository 
type BaseRepository struct {
	Model interface{}
	Db    *gorm.DB
}

// PageInfo 
type PageInfo struct {
	Page     int64  `json:"page"`     // 
	PageSize int64  `json:"pageSize"` // 
	Keywords string `json:"keywords"` // 
}

// Insert 
// @param map[string]interface{} data 
// @return toMap err 
func (base *BaseRepository) Insert(data map[string]interface{}) (toMap map[string]interface{}, err error) {
	// IDID
	data["id"] = constants.InitId
	if err := mapstructure.WeakDecode(data, base.Model); err != nil {
		return nil, err
	}
	if err := base.Db.Create(base.Model).Error; err != nil {
		return nil, err
	}
	// 
	tempStruct := base.Model
	toMap, err = utils.ToMap(tempStruct, "json")
	if err != nil {
		return nil, err
	}
	return toMap, nil
}

// Find 
// @param map[string]interface{} condition 
// @param []string fields 
// @return data err 
func (base *BaseRepository) Find(condition map[string]interface{}, fields ...[]string) (info map[string]interface{}, err error) {
	data := make(map[string]interface{})
	info = make(map[string]interface{})
	if len(fields) > 0 {
		if err := base.Db.Model(&base.Model).Where(condition).Select(fields[0]).Find(&data).Error; err != nil {
			return nil, err
		}
	} else {
		if err := base.Db.Model(&base.Model).Where(condition).Find(&data).Error; err != nil {
			return nil, err
		}
	}
	if len(data) > 0 {
		for k, v := range data {
			// 
			info[utils.UnderscoreToLowerCamelCase(k)] = v
		}
	}
	return info, nil
}

// Update 
// @param []uint ids 
// @param map[string]interface{} attributes 
// @return error 
func (base *BaseRepository) Update(ids []uint, data map[string]interface{}) error {
	var attributes = make(map[string]interface{})
	// 
	for k, v := range data {
		k := utils.ToSnakeCase(k)
		attributes[k] = v
	}
	if err := base.Db.Model(&base.Model).Where("id IN (?)", ids).Updates(attributes).Error; err != nil {
		return err
	}
	return nil
}

// Delete 
// @param []uint ids ID
// @return error 
func (base *BaseRepository) Delete(ids []uint) error {
	if err := base.Db.Delete(&base.Model, ids).Error; err != nil {
		return err
	}
	return nil
}

// IsExist 
// @param map[string]interface{} condition 
// @return bool bool
func (base *BaseRepository) IsExist(condition map[string]interface{}) bool {
	data := make(map[string]interface{})
	err := base.Db.Model(&base.Model).Where(condition).Find(&data).Error
	if err != nil && len(data) > constants.LengthByZero {
		return true
	}
	return false
}

// GetList 
// @param string query 
// @param []string fields 
// @param string order 
// @param map[string]interface{} pageInfo 
// @return list total pageNum err 
func (base *BaseRepository) GetList(query string, fields []string, order string, pageInfo ...map[string]interface{}) (data map[string]interface{}, err error) {
	var (
		page    PageInfo
		total   int64
		pageNum int64
		list    []map[string]interface{}
	)
	if len(pageInfo) > 0 {
		if err := mapstructure.WeakDecode(pageInfo[0], &page); err != nil {
			panic(constants.MapToStruct)
		}
	}
	// 
	base.Db.Model(&base.Model).Count(&total)
	// 
	if page.PageSize != 0 && total%page.PageSize != 0 {
		pageNum = total / page.PageSize
		pageNum++
	}
	//  query = fmt.Sprintf(" dns like '%%%s' ", createDbnameInfo.DNS)
	err = base.Db.Model(&base.Model).
		Select(fields).
		Where(query).
		Order(order).
		Limit(int(page.PageSize)).
		Offset(int((page.Page - 1) * page.PageSize)).
		Find(&list).Error
	if err != nil {
		return nil, err
	}
	data = map[string]interface{}{
		"list":     list,          // 
		"total":    total,         // 
		"pageNum":  pageNum,       // 
		"pageSize": page.PageSize, // 
		"page":     page.Page,     // 
	}
	return data, nil
}

// GetAllData 
// @param []string fields 
// @return list err 
func (base *BaseRepository) GetAllData(fields []string) (data []map[string]interface{}, err error) {
	if len(fields) > 0 {
		if err := base.Db.Model(&base.Model).Select(fields).Find(&data).Error; err != nil {
			return nil, err
		}
	} else {
		if err := base.Db.Model(&base.Model).Find(&data).Error; err != nil {
			return nil, err
		}
	}
	return data, nil
}

ModelBaseRepository

package repositories

import (
	AdminConstant "github.com/herman-hang/herman/app/constants/admin"
	"github.com/herman-hang/herman/app/models"
	"github.com/herman-hang/herman/kernel/core"
	"gorm.io/gorm"
)

// AdminRepository 
type AdminRepository struct {
	BaseRepository
}

// Admin 
// @param *gorm.DB tx 
// @return AdminRepository 
func Admin(tx ...*gorm.DB) *AdminRepository {
	if len(tx) > 0 && tx[0] != nil {
		return &AdminRepository{BaseRepository{Model: new(models.Admin), Db: tx[0]}}
	}

	return &AdminRepository{BaseRepository{Model: new(models.Admin), Db: core.Db}}
}

// GetAdminInfo 
// @param interface{} attributes iduser
// @return admin 
func (u AdminRepository) GetAdminInfo(attributes interface{}) (admin *models.Admin) {
	var err error
	switch attributes.(type) {
	case uint:
		err = core.Db.Where("id = ?", attributes).Find(&admin).Error
	case string:
		err = core.Db.Where("user = ?", attributes).Find(&admin).Error

	}
	if err != nil {
		panic(AdminConstant.GetAdminInfoFail)
	}

	return admin
}
// AdminRepository 
type AdminRepository struct {
	BaseRepository
}
// Admin 
// @param *gorm.DB tx 
// @return AdminRepository 
func Admin(tx ...*gorm.DB) *AdminRepository {
	if len(tx) > 0 && tx[0] != nil {
		return &AdminRepository{BaseRepository{Model: new(models.Admin), Db: tx[0]}}
	}

	return &AdminRepository{BaseRepository{Model: new(models.Admin), Db: core.Db}}
}

Service

// 
admin := repositories.Admin().GetAdminInfo(fmt.Sprintf("%s", data["user"]))

repositories.Admin()GORMGORMhttps://gorm.io/zh_CN/docs/

8.

jsongormcolumn

package models

import (
	"gorm.io/gorm"
	"time"
)

// Admin 
type Admin struct {
	Id           uint           `json:"id" gorm:"column:id;primary_key;comment:ID"`
	User         string         `json:"user" gorm:"column:user;comment:"`
	Password     string         `json:"password" gorm:"column:password;comment:"`
	Photo        string         `json:"photo" gorm:"column:photo;comment:"`
	Name         string         `json:"name" gorm:"column:name;comment:"`
	Card         string         `json:"card" gorm:"column:card;comment:"`
	Sex          uint8          `json:"sex" gorm:"column:sex;default:3;comment:(1,23)"`
	Age          uint8          `json:"age" gorm:"column:age;default:0;comment:"`
	Region       string         `json:"region" gorm:"column:region;comment:"`
	Phone        string         `json:"phone" gorm:"column:phone;comment:"`
	Email        string         `json:"email" gorm:"column:email;comment:"`
	Introduction string         `json:"introduction" gorm:"column:introduction;comment:"`
	State        uint8          `json:"state" gorm:"column:state;default:2;comment:(1,2)"`
	Sort         uint           `json:"sort" gorm:"column:sort;default:0;comment:"`
	LoginOutIp   string         `json:"loginOutIp" gorm:"column:login_out_ip;comment:IP"`
	LoginTotal   uint           `json:"loginTotal" gorm:"column:login_total;default:0;comment:"`
	LoginOutAt   time.Time      `json:"loginOutAt" gorm:"column:login_out_at;default:1970-01-01 00:00:00;comment:"`
	CreatedAt    time.Time      `json:"createdAt" gorm:"column:created_at;comment:"`
	UpdatedAt    time.Time      `json:"updatedAt" gorm:"column:updated_at;comment:"`
	DeletedAt    gorm.DeletedAt `json:"deletedAt" gorm:"column:deleted_at;index;comment:"`
}

// TableName 
func (Admin) TableName() string {
	return "admin"
}

TableName()string

9.

/app/response.go

package app

import (
	"fmt"
	"github.com/herman-hang/herman/app/constants"
	"github.com/herman-hang/herman/app/utils"
	"net/http"
)

// Response 
type Response struct {
	HttpCode int         `json:"-"`
	Code     int         `json:"code"`
	Message  string      `json:"message"`
	Data     interface{} `json:"data"`
}

// Option 
type Option func(*Response)

// C JSON
// @param int code 
// @return Option 
func C(code int) Option {
	return func(this *Response) {
		this.Code = code
	}
}

// M 
// @param string message 
// @return Option 
func M(message string) Option {
	return func(this *Response) {
		this.Message = message
	}
}

// D 
// @param interface{} data 
// @return Option 
func D(data interface{}) Option {
	return func(this *Response) {
		this.Data = data
	}
}

// H HTTP
// @param int HttpCode HTTP200500
// @return Option 
func H(HttpCode int) Option {
	return func(this *Response) {
		this.HttpCode = HttpCode
	}
}

// Success 
// @param *Gin g 
// @param Option opts CMDH
func (r *Request) Success(opts ...Option) {
	defaultResponse := &Response{
		HttpCode: http.StatusOK,
		Code:     http.StatusOK,
		Message:  constants.Success,
		Data:     nil,
	}

	// opts
	for _, o := range opts {
		o(defaultResponse)
	}
	// http
	r.Context.JSON(defaultResponse.HttpCode, defaultResponse)
	return
}

// Json 
// @param interface{} data 
// @param args messagecode
func (r *Request) Json(data interface{}, args ...interface{}) {
	var jsonString []byte
	// json
	camelJson, _ := utils.CamelJSON(data)
	switch len(args) {
	case 0:
		jsonString = []byte(fmt.Sprintf(`{"code":%d,"message":"%s","data":%s}`, http.StatusOK, constants.Success, camelJson))
	case 1:
		jsonString = []byte(fmt.Sprintf(`{"code":%d,"message":"%s","data":%s}`, http.StatusOK, args[0], camelJson))
	case 2:
		jsonString = []byte(fmt.Sprintf(`{"code":%d,"message":"%s","data":%s}`, args[1], args[0], camelJson))
	}
	// http
	r.Context.Data(http.StatusOK, "application/json", jsonString)
}

json2

	// 
	rootEngine.GET("/", func(context *gin.Context) {
		response := app.Request{Context: context}
		response.Success(app.D(map[string]interface{}{
			"message": "Welcome to Herman!",
		}))
	})

response.Success()4app.D()``response.Success()``app.H()``app.C()``app.M()

Json

func Login(ctx *gin.Context) {
   context := app.Request{Context: ctx}
   data := context.Params()
   context.Json(AdminService.Login(AdminValidate.Login(data)), AdminConstant.LoginSuccess)
}

context.Json()datamessagecode

10.

/tests/base.go``testsHTTP/tests/base.go

// AdminLogin 
// @return void
func (s *SuiteCase) AdminLogin() {
	var (
		response app.Response
		loginUri = s.AppPrefix + "/admin/login"
	)
	// mapjson
	_, _, w := s.Request("POST", loginUri, map[string]interface{}{
		"user":     "admin",
		"password": "123456",
	})
	// jsonstruct
	_ = json.Unmarshal(w.Body.Bytes(), &response)
	s.Authorization = response.Data.(string)
}

SetupSuite()

// SetupSuite 
// @return void
func (s *SuiteCase) SetupSuite() {
	app.InitConfig()
	servers.ZapLogs()
	middlewares.Reload()
	gin.SetMode(app.Config.Mode)
	e := gin.Default()
	e.Use(middlewares.CatchError())
	core.Engine = routers.InitRouter(e)
	s.AppPrefix = app.Config.AppPrefix
	switch s.Guard {
	case "admin":
		s.AdminLogin()
	default:
		panic(MiddlewareConstant.GuardError)
	}
}
// TestAdminTestSuite 
// @return void
func TestAdminTestSuite(t *testing.T) {
   suite.Run(t, &AdminTestSuite{SuiteCase: test.SuiteCase{Guard: "admin"}})
}

Guard: "admin"

package admin

import (
   "fmt"
   "github.com/brianvoe/gofakeit/v6"
   "github.com/herman-hang/herman/app/repositories"
   "github.com/herman-hang/herman/kernel/core/test"
   "github.com/herman-hang/herman/database/seeders/admin"
   "github.com/herman-hang/herman/database/seeders/role"
   "github.com/stretchr/testify/suite"
   "testing"
)

// 
type AdminTestSuite struct {
   test.SuiteCase
}

var (
   AdminLoginUri = "/admin/login"  // URI
   AdminUri      = "/admin/admins" // URI
)

// TestLogin 
// @return void
func (base *AdminTestSuite) TestLogin() {
   base.Assert([]test.Case{
      {
         Method:  "POST",
         Uri:     base.AppPrefix + AdminLoginUri,
         Params:  map[string]interface{}{"user": "admin", "password": "123456"},
         Code:    200,
         Message: "",
      },
   })
}

// TestAddAdmin 
// @return void
func (base *AdminTestSuite) TestAddAdmin() {
   roleInfo, _ := repositories.Role().Insert(role.Role())
   adminInfo := admin.Admin()
   adminInfo["roles"] = []map[string]interface{}{
      {
         "name": roleInfo["name"].(string),
         "role": roleInfo["role"].(string),
      },
   }
   base.Assert([]test.Case{
      {
         Method:  "POST",
         Uri:     base.AppPrefix + AdminUri,
         Params:  adminInfo,
         Code:    200,
         Message: "",
      },
   })
}

// TestModifyAdmin 
// @return void
func (base *AdminTestSuite) TestModifyAdmin() {
   roleInfo, _ := repositories.Role().Insert(role.Role())
   adminInfo := admin.Admin()
   adminInfo["roles"] = []map[string]interface{}{
      {
         "name": roleInfo["name"].(string),
         "role": roleInfo["role"].(string),
      },
   }
   info, _ := repositories.Admin().Insert(adminInfo)
   base.Assert([]test.Case{
      {
         Method: "PUT",
         Uri:    base.AppPrefix + AdminUri,
         Params: map[string]interface{}{
            "id":           info["id"],
            "user":         gofakeit.Username(),
            "password":     gofakeit.Password(false, false, true, false, false, 10),
            "photo":        gofakeit.ImageURL(100, 100),
            "roles":        adminInfo["roles"],
            "name":         gofakeit.Name(),
            "card":         "450981200008272525",
            "sex":          gofakeit.RandomInt([]int{1, 2, 3}),
            "age":          gofakeit.Number(18, 60),
            "region":       gofakeit.Country(),
            "phone":        "18888888888",
            "email":        gofakeit.Email(),
            "introduction": gofakeit.Sentence(10),
            "state":        gofakeit.RandomInt([]int{1, 2}),
            "sort":         gofakeit.Number(1, 100),
         },
         Code:    200,
         Message: "",
      },
   })
}

// TestDeleteAdmin ID
// @return void
func (base *AdminTestSuite) TestFindAdmin() {
   roleInfo, _ := repositories.Role().Insert(role.Role())
   adminInfo := admin.Admin()
   adminInfo["roles"] = []map[string]interface{}{
      {
         "name": roleInfo["name"].(string),
         "role": roleInfo["role"].(string),
      },
   }
   info, _ := repositories.Admin().Insert(adminInfo)
   base.Assert([]test.Case{
      {
         Method:  "GET",
         Uri:     base.AppPrefix + AdminUri + "/" + fmt.Sprintf("%d", info["id"]),
         Params:  nil,
         Code:    200,
         Message: "",
      },
   })
}

// TestGetAdminList 
// @return void
func (base *AdminTestSuite) TestRemoveAdmin() {
   roleInfo, _ := repositories.Role().Insert(role.Role())
   adminInfo := admin.Admin()
   adminInfo["roles"] = []map[string]interface{}{
      {
         "name": roleInfo["name"].(string),
         "role": roleInfo["role"].(string),
      },
   }
   info, _ := repositories.Admin().Insert(adminInfo)
   base.Assert([]test.Case{
      {
         Method: "DELETE",
         Uri:    base.AppPrefix + AdminUri,
         Params: map[string]interface{}{
            "id": []uint{info["id"].(uint)},
         },
         Code:    200,
         Message: "",
      },
   })
}

// TestGetAdminList 
// @return void
func (base *AdminTestSuite) TestListAdmin() {
   roleInfo, _ := repositories.Role().Insert(role.Role())
   adminInfo := admin.Admin()
   adminInfo["roles"] = []map[string]interface{}{
      {
         "name": roleInfo["name"].(string),
         "role": roleInfo["role"].(string),
      },
   }
   _, _ = repositories.Admin().Insert(adminInfo)
   base.Assert([]test.Case{
      {
         Method:  "GET",
         Uri:     base.AppPrefix + AdminUri,
         Params:  map[string]interface{}{"page": 1, "pageSize": 2, "keywords": ""},
         Code:    200,
         Message: "",
         IsList:  true,
         Fields: []string{
            "id",
            "user",
            "photo",
            "sort",
            "state",
            "phone",
            "email",
            "name",
            "card",
            "introduction",
            "sex",
            "age",
            "region",
            "createdAt",
         },
      },
   })
}

// TestAdminTestSuite 
// @return void
func TestAdminTestSuite(t *testing.T) {
   suite.Run(t, &AdminTestSuite{SuiteCase: test.SuiteCase{Guard: "admin"}})
}

11.

/database/migrations``__.sql``1_init.up.sql``1_init.down.sql(DDL)

herman migrate --status=true --direction=up
herman migrate -s true
herman migrate -s true -d up
herman migrate --status=true --direction=down
herman migrate -s true -d down
herman migrate --status=true --direction=force --version=1 # 1
herman migrate -s true -d force -v 1 # 1
  • 1
herman migrate --status=true --direction=up --number=1
herman migrate -s true -d up -n 1
  • 1
herman migrate --status=true --direction=down --number=1
herman migrate -s true -d down -n 1
herman migrate --status=true --direction=drop
herman migrate -s true -d drop

12.

/database/seeders``brianvoe/gofakeit

package admin

import (
   "github.com/brianvoe/gofakeit/v6"
)

// Admin 
func Admin() map[string]interface{} {
   return map[string]interface{}{
      "user":         gofakeit.Username(),
      "password":     gofakeit.Password(false, false, true, false, false, 10),
      "photo":        gofakeit.ImageURL(100, 100),
      "name":         gofakeit.Name(),
      "card":         "450981200008272525",
      "sex":          gofakeit.RandomInt([]int{1, 2, 3}),
      "age":          gofakeit.Number(18, 60),
      "region":       gofakeit.Country(),
      "phone":        "18888888888",
      "email":        gofakeit.Email(),
      "introduction": gofakeit.Sentence(10),
      "state":        gofakeit.RandomInt([]int{1, 2}),
      "sort":         gofakeit.Number(1, 100),
   }
}

https://github.com/brianvoe/gofakeit

13.

12Herman

// WatermarkConfig 
type WatermarkConfig struct {
	FontSize int   // 
	Color    color.RGBA  // rgba
	Text     string // 
}

type BlockPuzzleConfig struct {
	Offset int //  
}

type ClickWordConfig struct {
	FontSize int // 
	FontNum  int //  
}

type Config struct {
	Watermark      *WatermarkConfig
	ClickWord      *ClickWordConfig
	BlockPuzzle    *BlockPuzzleConfig
	CacheType      string // 
	CacheExpireSec int
}

func NewConfig() *Config {
	return &Config{
		CacheType: "redis",  // 
		Watermark: &WatermarkConfig{
			FontSize: 12,
			Color:    color.RGBA{R: 255, G: 255, B: 255, A: 255},
			Text:     "",
		},
		ClickWord: &ClickWordConfig{
			FontSize: 25,
			FontNum:  5,
		},
		BlockPuzzle:    &BlockPuzzleConfig{Offset: 10},
		CacheExpireSec: 2 * 60, // 
	}
}
package main

import (
	config2 "github.com/TestsLing/aj-captcha-go/config"
	"github.com/TestsLing/aj-captcha-go/service"
	"github.com/TestsLing/aj-captcha-go/const"
	"github.com/gin-gonic/gin"
)

//  
type clientParams struct {
	Token       string `json:"token"`
	PointJson   string `json:"pointJson"`
	CaptchaType string `json:"captchaType"`
}

// 
var config = config2.NewConfig()
//   
var factory = service.NewCaptchaServiceFactory(config)

func main() {

	//   CacheType key
	factory.RegisterCache(constant.MemCacheKey, service.NewMemCacheService(20)) // 20
	
	//  
	factory.RegisterService(constant.ClickWordCaptcha, service.NewClickWordCaptchaService(factory))
	factory.RegisterService(constant.BlockPuzzleCaptcha, service.NewBlockPuzzleCaptchaService(factory))

	//Default
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		//json
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.GET("/captcha/get", func(c *gin.Context) {
		// 
		data := factory.GetService(config2.BlockPuzzleCaptcha).Get()
		//json
		c.JSON(200, data)
	})
	r.Run("0.0.0.0:888") // listen and serve on 0.0.0.0:888
}

https://ajcaptcha.beliefteam.cn/captcha-doc/

14. License

Apache License Version 2.0 see http://www.apache.org/licenses/LICENSE-2.0.html