Browse Source

first commit

lijian 2 years ago
commit
0636073c63
52 changed files with 2397 additions and 0 deletions
  1. 1 0
      .gitattributes
  2. 20 0
      .gitignore
  3. 26 0
      Dockerfile
  4. 3 0
      README.MD
  5. 22 0
      app/bll/b_demo.go
  6. 9 0
      app/bll/b_trans.go
  7. 14 0
      app/bll/impl/impl.go
  8. 44 0
      app/bll/impl/internal/b_common.go
  9. 112 0
      app/bll/impl/internal/b_demo.go
  10. 23 0
      app/bll/impl/internal/b_trans.go
  11. 69 0
      app/context/context.go
  12. 22 0
      app/errors/error.go
  13. 94 0
      app/errors/response.go
  14. 0 0
      app/model/.gitkeep
  15. 65 0
      app/model/entity/e_demo.go
  16. 26 0
      app/model/entity/entity.go
  17. 110 0
      app/model/impl/model/m_demo.go
  18. 54 0
      app/model/impl/model/m_trans.go
  19. 124 0
      app/model/impl/model/model.go
  20. 22 0
      app/model/m_demo.go
  21. 13 0
      app/model/m_trans.go
  22. 33 0
      app/schema/s_demo.go
  23. 54 0
      app/schema/schema.go
  24. 151 0
      boot/boot.go
  25. 67 0
      boot/gorm.go
  26. 104 0
      config/config.toml
  27. 0 0
      docker/.gitkeep
  28. 0 0
      document/.gitkeep
  29. 13 0
      go.mod
  30. 168 0
      library/auth/jwt_auth.go
  31. 182 0
      library/gplus/gplus.go
  32. 64 0
      library/logger/gorm_log.go
  33. 199 0
      library/logger/logger.go
  34. 48 0
      library/middleware/middleware.go
  35. 39 0
      library/middleware/mw_auth.go
  36. 71 0
      library/middleware/mw_rate_limiter.go
  37. 20 0
      library/middleware/mw_trace.go
  38. 36 0
      library/redis/redis.go
  39. 39 0
      library/utils/util.go
  40. 11 0
      main.go
  41. 0 0
      public/html/.gitkeep
  42. 0 0
      public/plugin/.gitkeep
  43. 0 0
      public/resource/css/.gitkeep
  44. 0 0
      public/resource/image/.gitkeep
  45. 0 0
      public/resource/js/.gitkeep
  46. 27 0
      router/api/api.go
  47. 41 0
      router/api/controllers/c_demo.go
  48. 8 0
      router/api/controllers/ctl.go
  49. 15 0
      router/router.go
  50. 18 0
      router/swagger.go
  51. 116 0
      swagger/swagger.json
  52. 0 0
      template/.gitkeep

+ 1 - 0
.gitattributes

@@ -0,0 +1 @@
+* linguist-language=GO

+ 20 - 0
.gitignore

@@ -0,0 +1,20 @@
+.buildpath
+.hgignore.swp
+.project
+.orig
+.swp
+.idea/
+.settings/
+.vscode/
+vender/
+composer.lock
+gitpush.sh
+# only ignore /{log,pkg,bin,cbuild}
+/log
+/pkg
+/bin
+/cbuild
+**/.DS_Store
+main
+.vscode
+go.sum

+ 26 - 0
Dockerfile

@@ -0,0 +1,26 @@
+FROM loads/alpine:3.8
+
+LABEL maintainer="john@goframe.org"
+
+###############################################################################
+#                                INSTALLATION
+###############################################################################
+
+# 设置固定的项目路径
+ENV WORKDIR /var/www/gxt-api-frame
+
+# 添加应用可执行文件,并设置执行权限
+ADD ./bin/linux_amd64/main   $WORKDIR/main
+RUN chmod +x $WORKDIR/main
+
+# 添加I18N多语言文件、静态文件、配置文件、模板文件
+ADD i18n     $WORKDIR/i18n
+ADD public   $WORKDIR/public
+ADD config   $WORKDIR/config
+ADD template $WORKDIR/template
+
+###############################################################################
+#                                   START
+###############################################################################
+WORKDIR $WORKDIR
+CMD ./main

+ 3 - 0
README.MD

@@ -0,0 +1,3 @@
+# 高新通后端基础框架
+
+框架文档地址:https://goframe.org/display/gf

+ 22 - 0
app/bll/b_demo.go

@@ -0,0 +1,22 @@
+package bll
+
+import (
+	"context"
+	"gxt-api-frame/app/schema"
+)
+
+// IDemo demo业务逻辑接口
+type IDemo interface {
+	// 查询数据
+	Query(ctx context.Context, params schema.DemoQueryParam, opts ...schema.DemoQueryOptions) (*schema.DemoQueryResult, error)
+	// 查询指定数据
+	Get(ctx context.Context, recordID string, opts ...schema.DemoQueryOptions) (*schema.Demo, error)
+	// 创建数据
+	Create(ctx context.Context, item schema.Demo) (*schema.Demo, error)
+	// 更新数据
+	Update(ctx context.Context, recordID string, item schema.Demo) (*schema.Demo, error)
+	// 删除数据
+	Delete(ctx context.Context, recordID string) error
+	// 更新状态
+	UpdateStatus(ctx context.Context, recordID string, status int) error
+}

+ 9 - 0
app/bll/b_trans.go

@@ -0,0 +1,9 @@
+package bll
+
+import "context"
+
+// ITrans 事务管理接口
+type ITrans interface {
+	// 执行事务
+	Exec(ctx context.Context, fn func(context.Context) error) error
+}

+ 14 - 0
app/bll/impl/impl.go

@@ -0,0 +1,14 @@
+package impl
+
+import (
+	"go.uber.org/dig"
+	"gxt-api-frame/app/bll"
+	"gxt-api-frame/app/bll/impl/internal"
+)
+
+func Inject(container *dig.Container) {
+	_ = container.Provide(internal.NewTrans)
+	_ = container.Provide(func(b *internal.Trans) bll.ITrans { return b })
+	_ = container.Provide(internal.NewDemo)
+	_ = container.Provide(func(b *internal.Demo) bll.IDemo { return b })
+}

+ 44 - 0
app/bll/impl/internal/b_common.go

@@ -0,0 +1,44 @@
+package internal
+
+import (
+	"context"
+	iContext "gxt-api-frame/app/context"
+	"gxt-api-frame/app/model"
+)
+
+// TransFunc 定义事务执行函数
+type TransFunc func(context.Context) error
+
+// ExecTrans 执行事务
+func ExecTrans(ctx context.Context, transModel model.ITrans, fn TransFunc) error {
+	if _, ok := iContext.FromTrans(ctx); ok {
+		return fn(ctx)
+	}
+	trans, err := transModel.Begin(ctx)
+	if err != nil {
+		return err
+	}
+
+	defer func() {
+		if r := recover(); r != nil {
+			_ = transModel.Rollback(ctx, trans)
+			panic(r)
+		}
+	}()
+
+	err = fn(iContext.NewTrans(ctx, trans))
+	if err != nil {
+		_ = transModel.Rollback(ctx, trans)
+		return err
+	}
+	return transModel.Commit(ctx, trans)
+
+}
+
+// ExecTransWithLock 执行事务(加锁)
+func ExecTransWithLock(ctx context.Context, transModel model.ITrans, fn TransFunc) error {
+	if !iContext.FromTransLock(ctx) {
+		ctx = iContext.NewTransLock(ctx)
+	}
+	return ExecTrans(ctx, transModel, fn)
+}

+ 112 - 0
app/bll/impl/internal/b_demo.go

@@ -0,0 +1,112 @@
+package internal
+
+import (
+	"context"
+	"github.com/gogf/gf/util/guid"
+	"gxt-api-frame/app/errors"
+	"gxt-api-frame/app/model"
+	"gxt-api-frame/app/schema"
+)
+
+// NewDemo 创建demo
+func NewDemo(mDemo model.IDemo) *Demo {
+	return &Demo{
+		DemoModel: mDemo,
+	}
+}
+
+// Demo 示例程序
+type Demo struct {
+	DemoModel model.IDemo
+}
+
+// Query 查询数据
+func (a *Demo) Query(ctx context.Context, params schema.DemoQueryParam, opts ...schema.DemoQueryOptions) (*schema.DemoQueryResult, error) {
+	return a.DemoModel.Query(ctx, params, opts...)
+}
+
+// Get 查询指定数据
+func (a *Demo) Get(ctx context.Context, recordID string, opts ...schema.DemoQueryOptions) (*schema.Demo, error) {
+	item, err := a.DemoModel.Get(ctx, recordID, opts...)
+	if err != nil {
+		return nil, err
+	} else if item == nil {
+		return nil, errors.ErrNotFound
+	}
+	return item, nil
+}
+
+func (a *Demo) checkCode(ctx context.Context, code string) error {
+	result, err := a.DemoModel.Query(ctx, schema.DemoQueryParam{
+		Code: code,
+	}, schema.DemoQueryOptions{
+		PageParam: &schema.PaginationParam{PageSize: -1},
+	})
+	if err != nil {
+		return err
+	} else if result.PageResult.Total > 0 {
+		return errors.New400Response("编号已经存在")
+	}
+	return nil
+}
+
+func (a *Demo) getUpdate(ctx context.Context, recordID string) (*schema.Demo, error) {
+	return a.Get(ctx, recordID)
+}
+
+// Create 创建数据
+func (a *Demo) Create(ctx context.Context, item schema.Demo) (*schema.Demo, error) {
+	err := a.checkCode(ctx, item.Code)
+	if err != nil {
+		return nil, err
+	}
+	item.RecordID = guid.S()
+	err = a.DemoModel.Create(ctx, item)
+	if err != nil {
+		return nil, err
+	}
+	return a.getUpdate(ctx, item.RecordID)
+}
+
+// Update 更新数据
+func (a *Demo) Update(ctx context.Context, recordID string, item schema.Demo) (*schema.Demo, error) {
+	oldItem, err := a.DemoModel.Get(ctx, recordID)
+	if err != nil {
+		return nil, err
+	} else if oldItem == nil {
+		return nil, errors.ErrNotFound
+	} else if oldItem.Code != item.Code {
+		err := a.checkCode(ctx, item.Code)
+		if err != nil {
+			return nil, err
+		}
+	}
+	err = a.DemoModel.Update(ctx, recordID, item)
+	if err != nil {
+		return nil, err
+	}
+	return a.getUpdate(ctx, recordID)
+}
+
+// Delete 删除数据
+func (a *Demo) Delete(ctx context.Context, recordID string) error {
+	oldItem, err := a.DemoModel.Get(ctx, recordID)
+	if err != nil {
+		return err
+	} else if oldItem == nil {
+		return errors.ErrNotFound
+	}
+
+	return a.DemoModel.Delete(ctx, recordID)
+}
+
+// UpdateStatus 更新状态
+func (a *Demo) UpdateStatus(ctx context.Context, recordID string, status int) error {
+	oldItem, err := a.DemoModel.Get(ctx, recordID)
+	if err != nil {
+		return err
+	} else if oldItem == nil {
+		return errors.ErrNotFound
+	}
+	return a.DemoModel.UpdateStatus(ctx, recordID, status)
+}

+ 23 - 0
app/bll/impl/internal/b_trans.go

@@ -0,0 +1,23 @@
+package internal
+
+import (
+	"context"
+	"gxt-api-frame/app/model"
+)
+
+// NewTrans 创建事务管理实例
+func NewTrans(trans model.ITrans) *Trans {
+	return &Trans{
+		TransModel: trans,
+	}
+}
+
+// Trans 事务管理
+type Trans struct {
+	TransModel model.ITrans
+}
+
+// Exec 执行事务
+func (a *Trans) Exec(ctx context.Context, fn func(context.Context) error) error {
+	return ExecTrans(ctx, a.TransModel, fn)
+}

+ 69 - 0
app/context/context.go

@@ -0,0 +1,69 @@
+package context
+
+import (
+	"context"
+)
+
+// 定义全局上下文中的键
+type (
+	transCtx        struct{}
+	transLockCtx    struct{}
+	userIDCtx       struct{}
+	traceIDCtx      struct{}
+)
+
+// NewTrans 创建事务的上下文
+func NewTrans(ctx context.Context, trans interface{}) context.Context {
+	return context.WithValue(ctx, transCtx{}, trans)
+}
+
+// FromTrans 从上下文中获取事务
+func FromTrans(ctx context.Context) (interface{}, bool) {
+	v := ctx.Value(transCtx{})
+	return v, v != nil
+}
+
+// NewTransLock 创建事务锁的上下文
+func NewTransLock(ctx context.Context) context.Context {
+	return context.WithValue(ctx, transLockCtx{}, struct{}{})
+}
+
+// FromTransLock 从上下文中获取事务锁
+func FromTransLock(ctx context.Context) bool {
+	v := ctx.Value(transLockCtx{})
+	return v != nil
+}
+
+// NewUserID 创建用户ID的上下文
+func NewUserID(ctx context.Context, userID string) context.Context {
+	return context.WithValue(ctx, userIDCtx{}, userID)
+}
+
+// FromUserID 从上下文中获取用户ID
+func FromUserID(ctx context.Context) (string, bool) {
+	v := ctx.Value(userIDCtx{})
+	if v != nil {
+		if s, ok := v.(string); ok {
+			return s, s != ""
+		}
+	}
+	return "", false
+}
+
+
+// NewTraceID 创建追踪ID的上下文
+func NewTraceID(ctx context.Context, traceID string) context.Context {
+	return context.WithValue(ctx, traceIDCtx{}, traceID)
+}
+
+// FromTraceID 从上下文中获取追踪ID
+func FromTraceID(ctx context.Context) (string, bool) {
+	v := ctx.Value(traceIDCtx{})
+	if v != nil {
+		if s, ok := v.(string); ok {
+			return s, s != ""
+		}
+	}
+	return "", false
+}
+

+ 22 - 0
app/errors/error.go

@@ -0,0 +1,22 @@
+package errors
+
+import "github.com/gogf/gf/errors/gerror"
+
+// 定义别名
+var (
+	New       = gerror.New
+	Wrap      = gerror.Wrap
+	Wrapf     = gerror.Wrapf
+	WithStack = gerror.Stack
+)
+
+var (
+	ErrBadRequest              = New400Response("请求发生错误")
+
+	ErrNoPerm                = NewResponse(401, "无访问权限", 401)
+	ErrInvalidToken          = NewResponse(9999, "令牌失效", 401)
+	ErrNotFound              = NewResponse(404, "资源不存在", 404)
+	ErrTooManyRequests       = NewResponse(429, "请求过于频繁", 429)
+	ErrInternalServer        = NewResponse(500, "服务器发生错误", 500)
+	ErrDBServerInternalError = NewResponse(50001, "数据库发生错误", 500)
+)

+ 94 - 0
app/errors/response.go

@@ -0,0 +1,94 @@
+package errors
+
+import "github.com/gogf/gf/net/ghttp"
+
+// ResponseError 响应错误
+type ResponseError struct {
+	Code       int    // 错误码
+	Message    string // 错误消息
+	StatusCode int    // 响应错误码
+	ERR        error  // 响应错误
+}
+
+func (r *ResponseError) Error() string {
+	if r.ERR != nil {
+		return r.ERR.Error()
+	}
+	return r.Message
+}
+
+// UnWrapResponse 解包响应错误
+func UnWrapResponse(err error) *ResponseError {
+	if v, ok := err.(*ResponseError); ok {
+		return v
+	}
+	return nil
+}
+
+// WrapResponse 包装响应错误
+func WrapResponse(err error, code int, msg string, status ...int) error {
+	res := &ResponseError{
+		Code:    code,
+		Message: msg,
+		ERR:     err,
+	}
+	if len(status) > 0 {
+		res.StatusCode = status[0]
+	}
+	return res
+}
+
+// Wrap400Response 包装错误码为400的响应错误
+func Wrap400Response(err error, msg ...string) error {
+	m := "请求发生错误"
+	if len(msg) > 0 {
+		m = msg[0]
+	}
+	return WrapResponse(err, 400, m, 400)
+}
+
+// Wrap500Response 包装错误码为500的响应错误
+func Wrap500Response(err error, msg ...string) error {
+	m := "服务器发生错误"
+	if len(msg) > 0 {
+		m = msg[0]
+	}
+	return WrapResponse(err, 500, m, 500)
+}
+
+// NewResponse 创建响应错误
+func NewResponse(code int, msg string, status ...int) error {
+	res := &ResponseError{
+		Code:    code,
+		Message: msg,
+	}
+	if len(status) > 0 {
+		res.StatusCode = status[0]
+	}
+	return res
+}
+
+// New400Response 创建错误码为400的响应错误
+func New400Response(msg string) error {
+	return NewResponse(400, msg, 400)
+}
+
+// New500Response 创建错误码为500的响应错误
+func New500Response(msg string) error {
+	return NewResponse(500, msg, 500)
+}
+
+
+func Json(r *ghttp.Request, code int, v interface{}) {
+	if code == 500 || code == 401 {
+		r.Response.WriteStatus(code)
+		r.Response.ClearBuffer()
+	}
+	_ = r.Response.WriteJson(v)
+}
+
+// 返回JSON数据并退出当前HTTP执行函数。
+func JsonExit(r *ghttp.Request, err int, data interface{}) {
+	Json(r, err, data)
+	r.Exit()
+}

+ 0 - 0
app/model/.gitkeep


+ 65 - 0
app/model/entity/e_demo.go

@@ -0,0 +1,65 @@
+package entity
+
+import (
+	"context"
+	"gorm.io/gorm"
+	"gxt-api-frame/app/schema"
+)
+
+// GetDemoDB 获取demo存储
+func GetDemoDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
+	return getDBWithModel(ctx, defDB, Demo{})
+}
+
+// SchemaDemo demo对象
+type SchemaDemo schema.Demo
+
+// ToDemo 转换为demo实体
+func (a SchemaDemo) ToDemo() *Demo {
+	item := &Demo{
+		RecordID: a.RecordID,
+		Code:     &a.Code,
+		Name:     &a.Name,
+		Memo:     &a.Memo,
+		Status:   &a.Status,
+		Creator:  &a.Creator,
+	}
+	return item
+}
+
+// ToSchemaDemo 转换为demo对象
+func (a Demo) ToSchemaDemo() *schema.Demo {
+	item := &schema.Demo{
+		RecordID:  a.RecordID,
+		Code:      *a.Code,
+		Name:      *a.Name,
+		Memo:      *a.Memo,
+		Status:    *a.Status,
+		Creator:   *a.Creator,
+		CreatedAt: a.CreatedAt,
+	}
+	return item
+}
+
+// Demo demo实体
+type Demo struct {
+	gorm.Model
+	RecordID string  `gorm:"column:record_id;size:32;index;"` // 记录内码
+	Code     *string `gorm:"column:code;size:50;index;"`      // 编号
+	Name     *string `gorm:"column:name;size:100;index;"`     // 名称
+	Memo     *string `gorm:"column:memo;size:200;"`           // 备注
+	Status   *int    `gorm:"column:status;index;"`            // 状态(1:启用 2:停用)
+	Creator  *string `gorm:"column:creator;size:32;"`         // 创建者
+}
+
+// Demos demo列表
+type Demos []*Demo
+
+// ToSchemaDemos 转换为demo对象列表
+func (a Demos) ToSchemaDemos() []*schema.Demo {
+	list := make([]*schema.Demo, len(a))
+	for i, item := range a {
+		list[i] = item.ToSchemaDemo()
+	}
+	return list
+}

+ 26 - 0
app/model/entity/entity.go

@@ -0,0 +1,26 @@
+package entity
+
+import (
+	"context"
+	"gorm.io/gorm"
+	iContext "gxt-api-frame/app/context"
+)
+
+
+func getDB(ctx context.Context, defDB *gorm.DB) *gorm.DB {
+	trans, ok := iContext.FromTrans(ctx)
+	if ok {
+		db, ok := trans.(*gorm.DB)
+		if ok {
+			if iContext.FromTransLock(ctx) {
+				db = db.Set("gorm:query_option", "FOR UPDATE")
+			}
+			return db
+		}
+	}
+	return defDB
+}
+
+func getDBWithModel(ctx context.Context, defDB *gorm.DB, m interface{}) *gorm.DB {
+	return getDB(ctx, defDB).Model(m).WithContext(ctx)
+}

+ 110 - 0
app/model/impl/model/m_demo.go

@@ -0,0 +1,110 @@
+package model
+
+import (
+	"context"
+	"gorm.io/gorm"
+	"gxt-api-frame/app/errors"
+	"gxt-api-frame/app/model/entity"
+	"gxt-api-frame/app/schema"
+)
+
+// NewDemo 创建demo存储实例
+func NewDemo(db *gorm.DB) *Demo {
+	return &Demo{db}
+}
+
+// Demo demo存储
+type Demo struct {
+	db *gorm.DB
+}
+
+func (a *Demo) getQueryOption(opts ...schema.DemoQueryOptions) schema.DemoQueryOptions {
+	var opt schema.DemoQueryOptions
+	if len(opts) > 0 {
+		opt = opts[0]
+	}
+	return opt
+}
+
+// Query 查询数据
+func (a *Demo) Query(ctx context.Context, params schema.DemoQueryParam, opts ...schema.DemoQueryOptions) (*schema.DemoQueryResult, error) {
+	db := entity.GetDemoDB(ctx, a.db)
+	if v := params.Code; v != "" {
+		db = db.Where("code=?", v)
+	}
+	if v := params.LikeCode; v != "" {
+		db = db.Where("code LIKE ?", "%"+v+"%")
+	}
+	if v := params.LikeName; v != "" {
+		db = db.Where("name LIKE ?", "%"+v+"%")
+	}
+	if v := params.Status; v > 0 {
+		db = db.Where("status=?", v)
+	}
+	db = db.Order("id DESC")
+
+	opt := a.getQueryOption(opts...)
+	var list entity.Demos
+	pr, err := WrapPageQuery(ctx, db, opt.PageParam, &list)
+	if err != nil {
+		return nil, errors.ErrDBServerInternalError
+	}
+	qr := &schema.DemoQueryResult{
+		PageResult: pr,
+		Data:       list.ToSchemaDemos(),
+	}
+
+	return qr, nil
+}
+
+// Get 查询指定数据
+func (a *Demo) Get(ctx context.Context, recordID string, opts ...schema.DemoQueryOptions) (*schema.Demo, error) {
+	db := entity.GetDemoDB(ctx, a.db).Where("record_id=?", recordID)
+	var item entity.Demo
+	ok, err := FindOne(ctx, db, &item)
+	if err != nil {
+		return nil, errors.ErrDBServerInternalError
+	} else if !ok {
+		return nil, nil
+	}
+
+	return item.ToSchemaDemo(), nil
+}
+
+// Create 创建数据
+func (a *Demo) Create(ctx context.Context, item schema.Demo) error {
+	demo := entity.SchemaDemo(item).ToDemo()
+	result := entity.GetDemoDB(ctx, a.db).Create(demo)
+	if err := result.Error; err != nil {
+		return errors.ErrDBServerInternalError
+	}
+	return nil
+}
+
+// Update 更新数据
+func (a *Demo) Update(ctx context.Context, recordID string, item schema.Demo) error {
+	demo := entity.SchemaDemo(item).ToDemo()
+	result := entity.GetDemoDB(ctx, a.db).Where("record_id=?", recordID).Omit("record_id", "creator").Updates(demo)
+	if err := result.Error; err != nil {
+		return errors.ErrDBServerInternalError
+	}
+	return nil
+}
+
+// Delete 删除数据
+func (a *Demo) Delete(ctx context.Context, recordID string) error {
+	result := entity.GetDemoDB(ctx, a.db).Where("record_id=?", recordID).Delete(entity.Demo{})
+	if err := result.Error; err != nil {
+		return errors.ErrDBServerInternalError
+	}
+	return nil
+}
+
+// UpdateStatus 更新状态
+func (a *Demo) UpdateStatus(ctx context.Context, recordID string, status int) error {
+	result := entity.GetDemoDB(ctx, a.db).Where("record_id=?", recordID).Update("status", status)
+	if err := result.Error; err != nil {
+		return errors.ErrDBServerInternalError
+	}
+	return nil
+}

+ 54 - 0
app/model/impl/model/m_trans.go

@@ -0,0 +1,54 @@
+package model
+
+import (
+	"context"
+	"gorm.io/gorm"
+	"gxt-api-frame/app/errors"
+)
+
+// NewTrans 创建事务管理实例
+func NewTrans(db *gorm.DB) *Trans {
+	return &Trans{db}
+}
+
+// Trans 事务管理
+type Trans struct {
+	db *gorm.DB
+}
+
+// Begin 开启事务
+func (a *Trans) Begin(ctx context.Context) (interface{}, error) {
+	result := a.db.Begin()
+	if err := result.Error; err != nil {
+		return nil, errors.ErrDBServerInternalError
+	}
+	return result, nil
+}
+
+// Commit 提交事务
+func (a *Trans) Commit(ctx context.Context, trans interface{}) error {
+	db, ok := trans.(*gorm.DB)
+	if !ok {
+		return errors.New("unknow trans")
+	}
+
+	result := db.Commit()
+	if err := result.Error; err != nil {
+		return errors.ErrDBServerInternalError
+	}
+	return nil
+}
+
+// Rollback 回滚事务
+func (a *Trans) Rollback(ctx context.Context, trans interface{}) error {
+	db, ok := trans.(*gorm.DB)
+	if !ok {
+		return errors.New("unknow trans")
+	}
+
+	result := db.Rollback()
+	if err := result.Error; err != nil {
+		return errors.ErrDBServerInternalError
+	}
+	return nil
+}

+ 124 - 0
app/model/impl/model/model.go

@@ -0,0 +1,124 @@
+package model
+
+import (
+	"context"
+	"gorm.io/gorm"
+	iContext "gxt-api-frame/app/context"
+	"gxt-api-frame/app/schema"
+)
+
+// TransFunc 定义事务执行函数
+type TransFunc func(context.Context) error
+
+// ExecTrans 执行事务
+func ExecTrans(ctx context.Context, db *gorm.DB, fn TransFunc) error {
+	if _, ok := iContext.FromTrans(ctx); ok {
+		return fn(ctx)
+	}
+
+	transModel := NewTrans(db)
+	trans, err := transModel.Begin(ctx)
+	if err != nil {
+		return err
+	}
+
+	defer func() {
+		if r := recover(); r != nil {
+			_ = transModel.Rollback(ctx, trans)
+			panic(r)
+		}
+	}()
+
+	ctx = iContext.NewTrans(ctx, trans)
+	err = fn(ctx)
+	if err != nil {
+		_ = transModel.Rollback(ctx, trans)
+		return err
+	}
+	return transModel.Commit(ctx, trans)
+}
+
+// ExecTransWithLock 执行事务(加锁)
+func ExecTransWithLock(ctx context.Context, db *gorm.DB, fn TransFunc) error {
+	if !iContext.FromTransLock(ctx) {
+		ctx = iContext.NewTransLock(ctx)
+	}
+	return ExecTrans(ctx, db, fn)
+}
+
+// WrapPageQuery 包装带有分页的查询
+func WrapPageQuery(ctx context.Context, db *gorm.DB, pp *schema.PaginationParam, out interface{}) (*schema.PaginationResult, error) {
+	if pp != nil {
+		total, err := FindPage(ctx, db, pp.PageIndex, pp.PageSize, out)
+		if err != nil {
+			return nil, err
+		}
+		return &schema.PaginationResult{
+			Total: total,
+		}, nil
+	}
+
+	result := db.Find(out)
+	return nil, result.Error
+}
+
+// FindPage 查询分页数据
+func FindPage(ctx context.Context, db *gorm.DB, pageIndex, pageSize int, out interface{}) (int, error) {
+	selectTmp := db.Statement.Clauses["SELECT"]
+	var count int64
+	result := db.Count(&count)
+	db.Statement.Clauses["SELECT"] = selectTmp
+	if err := result.Error; err != nil {
+		return 0, err
+	} else if count == 0 {
+		return 0, nil
+	}
+
+	// 如果分页大小小于0或者分页索引小于0,则不查询数据
+	if pageSize < 0 || pageIndex < 0 {
+		return int(count), nil
+	}
+
+	if pageIndex > 0 && pageSize > 0 {
+		db = db.Offset((pageIndex - 1) * pageSize)
+	}
+	if pageSize > 0 {
+		db = db.Limit(pageSize)
+	}
+	result = db.Find(out)
+	if err := result.Error; err != nil {
+		return 0, err
+	}
+
+	return int(count), nil
+}
+
+// FindOne 查询单条数据
+func FindOne(ctx context.Context, db *gorm.DB, out interface{}) (bool, error) {
+	result := db.First(out)
+	if err := result.Error; err != nil {
+		if err == gorm.ErrRecordNotFound {
+			return false, nil
+		}
+		return false, err
+	}
+	return true, nil
+}
+
+// Check 检查数据是否存在
+func Check(ctx context.Context, db *gorm.DB) (bool, error) {
+	var count int64
+	result := db.Count(&count)
+	if err := result.Error; err != nil {
+		return false, err
+	}
+	return count > 0, nil
+}
+
+const maxNumberOfBatchParameters = 65535
+
+// OwnDb 实例化DB
+type OwnDb struct {
+	*gorm.DB
+}
+

+ 22 - 0
app/model/m_demo.go

@@ -0,0 +1,22 @@
+package model
+
+import (
+	"context"
+	"gxt-api-frame/app/schema"
+)
+
+// IDemo demo存储接口
+type IDemo interface {
+	// 查询数据
+	Query(ctx context.Context, params schema.DemoQueryParam, opts ...schema.DemoQueryOptions) (*schema.DemoQueryResult, error)
+	// 查询指定数据
+	Get(ctx context.Context, recordID string, opts ...schema.DemoQueryOptions) (*schema.Demo, error)
+	// 创建数据
+	Create(ctx context.Context, item schema.Demo) error
+	// 更新数据
+	Update(ctx context.Context, recordID string, item schema.Demo) error
+	// 删除数据
+	Delete(ctx context.Context, recordID string) error
+	// 更新状态
+	UpdateStatus(ctx context.Context, recordID string, status int) error
+}

+ 13 - 0
app/model/m_trans.go

@@ -0,0 +1,13 @@
+package model
+
+import "context"
+
+// ITrans 事务管理接口
+type ITrans interface {
+	// 开始事务
+	Begin(ctx context.Context) (interface{}, error)
+	// 提交事务
+	Commit(ctx context.Context, trans interface{}) error
+	// 回滚事务
+	Rollback(ctx context.Context, trans interface{}) error
+}

+ 33 - 0
app/schema/s_demo.go

@@ -0,0 +1,33 @@
+package schema
+
+import "time"
+
+// Demo demo对象
+type Demo struct {
+	RecordID  string    `json:"record_id"`                       // 记录ID
+	Code      string    `json:"code" v:"required"`            // 编号
+	Name      string    `json:"name" v:"required"`               // 名称
+	Memo      string    `json:"memo"`                            // 备注
+	Status    int       `json:"status" v:"required|max:2|min:1"` // 状态(1:启用 2:停用)
+	Creator   string    `json:"creator"`                         // 创建者
+	CreatedAt time.Time `json:"created_at"`                      // 创建时间
+}
+
+// DemoQueryParam 查询条件
+type DemoQueryParam struct {
+	Code     string `form:"-"`        // 编号
+	Status   int    `form:"status"`   // 状态(1:启用 2:停用)
+	LikeCode string `form:"likeCode"` // 编号(模糊查询)
+	LikeName string `form:"likeName"` // 名称(模糊查询)
+}
+
+// DemoQueryOptions demo对象查询可选参数项
+type DemoQueryOptions struct {
+	PageParam *PaginationParam // 分页参数
+}
+
+// DemoQueryResult demo对象查询结果
+type DemoQueryResult struct {
+	Data       []*Demo
+	PageResult *PaginationResult
+}

+ 54 - 0
app/schema/schema.go

@@ -0,0 +1,54 @@
+package schema
+
+// HTTPStatusText 定义HTTP状态文本
+type HTTPStatusText string
+
+func (t HTTPStatusText) String() string {
+	return string(t)
+}
+
+// HTTPError HTTP响应错误
+type HTTPError struct {
+	Error HTTPErrorItem `json:"error"` // 错误项
+}
+
+// HTTPErrorItem HTTP响应错误项
+type HTTPErrorItem struct {
+	Code    int    `json:"code"`     // 错误码
+	Message string `json:"message"`  // 错误信息
+	TraceId string `json:"trace_id"` // 追踪Id,用于快速定位错误
+}
+
+// HTTPStatus HTTP响应状态
+type HTTPStatus struct {
+	Status string `json:"status"` // 状态(OK)
+}
+
+// HTTPList HTTP响应列表数据
+type HTTPList struct {
+	List       interface{}     `json:"list"`
+	Pagination *HTTPPagination `json:"pagination,omitempty"`
+}
+
+// HTTPPagination HTTP分页数据
+type HTTPPagination struct {
+	Total    int `json:"total"`
+	Current  int `json:"current"`
+	PageSize int `json:"pageSize"`
+}
+
+// PaginationParam 分页查询条件
+type PaginationParam struct {
+	PageIndex int // 页索引
+	PageSize  int // 页大小
+}
+
+// PaginationResult 分页查询结果
+type PaginationResult struct {
+	Total int // 总数据条数
+}
+
+//VerifyToken 验证码令牌
+type VerifyToken struct {
+	Token string `json:"token"` //令牌
+}

+ 151 - 0
boot/boot.go

@@ -0,0 +1,151 @@
+package boot
+
+import (
+	"context"
+	"github.com/dgrijalva/jwt-go"
+	redisLib "github.com/go-redis/redis/v8"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"go.uber.org/dig"
+	"gorm.io/gorm"
+	"gxt-api-frame/app/bll/impl"
+	"gxt-api-frame/app/errors"
+	"gxt-api-frame/library/auth"
+	"gxt-api-frame/library/gplus"
+	"gxt-api-frame/library/logger"
+	"gxt-api-frame/library/middleware"
+	"gxt-api-frame/library/redis"
+	"gxt-api-frame/library/utils"
+	"gxt-api-frame/router"
+	"os"
+)
+
+// VERSION 定义应用版本号
+const VERSION = "1.0.0"
+
+func init() {
+	// 初始化logger
+	logger.Init(g.Cfg().GetString("common.run_mode"))
+	logger.SetVersion(VERSION)
+	logger.SetTraceIdFunc(utils.NewTraceID)
+	ctx := logger.NewTraceIDContext(context.Background(), utils.NewTraceID())
+	Init(ctx)
+}
+
+// 初始化App,
+// TODO: 返回释放回调,暂时没调用
+func Init(ctx context.Context) func() {
+	logger.Printf(ctx, "服务启动,运行模式:%s,版本号:%s,进程号:%d", g.Cfg().Get("common.run_mode"), VERSION, os.Getpid())
+	// 初始化依赖注入容器
+	container, call := buildContainer(ctx)
+	// 初始化路由注册
+	s := g.Server()
+	// 每个请求生成新的追踪Id,如果上下文件中没有trace-id
+	s.Use(middleware.TraceIdMiddleware())
+	// 统一处理内部错误
+	s.Use(func(r *ghttp.Request) {
+		r.Middleware.Next()
+		if err := r.GetError(); err != nil {
+			gplus.ResError(r, err)
+		}
+	})
+	_ = initJwtAuth(ctx, container)
+	router.InitRouters(s, container)
+	return func() {
+		if call != nil {
+			call()
+		}
+	}
+
+}
+// 初始化jwt认证,可以把相关配置放到config.toml中
+func initJwtAuth(ctx context.Context, container *dig.Container) error {
+	var opts []auth.Option
+	opts = append(opts, auth.SetExpired(g.Cfg().GetInt("jwt.expired")))
+	opts = append(opts, auth.SetSigningKey([]byte(g.Cfg().GetString("jwt.signing_key"))))
+	opts = append(opts, auth.SetKeyfunc(func(t *jwt.Token) (interface{}, error) {
+		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
+			return nil, errors.ErrInvalidToken
+		}
+		return []byte(g.Cfg().GetString("jwt.signing_key")), nil
+	}))
+
+	switch g.Cfg().GetString("jwt.signing_method") {
+	case "HS256":
+		opts = append(opts, auth.SetSigningMethod(jwt.SigningMethodHS256))
+	case "HS384":
+		opts = append(opts, auth.SetSigningMethod(jwt.SigningMethodHS384))
+	case "HS512":
+		opts = append(opts, auth.SetSigningMethod(jwt.SigningMethodHS512))
+	}
+	return container.Provide(func() auth.Auther { return auth.New(opts...)})
+}
+
+// 初始化redis
+func initRedis(ctx context.Context, container *dig.Container) func() {
+	addr := g.Cfg().GetString("redis.addr")
+	password := g.Cfg().GetString("redis.password")
+	db := g.Cfg().GetInt("redis.db")
+	redisCli := redis.Init(ctx, addr, password, db)
+	logger.Printf(ctx, "REDIS初始化成功,当前服务器[%s]", addr)
+	// 注入redis client
+	_ = container.Provide(func() *redisLib.Client {
+		return redisCli
+	})
+	return func() {
+		_ = redisCli.Close()
+	}
+}
+
+// 初始化存储,目前只初始化gorm
+func initStore(ctx context.Context, container *dig.Container) (func(), error) {
+	var storeCall func()
+	db, err := initGorm()
+	if err != nil {
+		return storeCall, err
+	}
+	// 如果自动映射数据表
+	if g.Cfg().GetBool("gorm.enable_auto_migrate") {
+		err = autoMigrate(db)
+		if err != nil {
+			return storeCall, err
+		}
+	}
+	// 注入DB
+	_ = container.Provide(func() *gorm.DB { return db })
+	// 注入model接口
+	_ = InjectModel(container)
+	storeCall = func() {
+		sqlDb, _ := db.DB()
+		_ = sqlDb.Close()
+	}
+	logger.Printf(ctx, "MYSQL初始化成功, 服务器[%s], 数据库[%s]",
+		g.Cfg().GetString("mysql.host"),
+		g.Cfg().GetString("mysql.db_name"))
+	return storeCall, nil
+}
+
+// 构建依赖注入容器
+func buildContainer(ctx context.Context) (*dig.Container, func()) {
+	container := dig.New()
+	// 初始化存储模块
+	storeCall, err := initStore(ctx, container)
+	if err != nil {
+		panic(err)
+	}
+	// 初始化redis模块
+	var redisCall func()
+	if g.Cfg().GetBool("redis.enable") {
+		redisCall = initRedis(ctx, container)
+	}
+	// 注入bll
+	impl.Inject(container)
+	return container, func() {
+		if storeCall != nil {
+			storeCall()
+		}
+		if redisCall != nil {
+			redisCall()
+		}
+	}
+}

+ 67 - 0
boot/gorm.go

@@ -0,0 +1,67 @@
+package boot
+
+import (
+	"fmt"
+	"gxt-api-frame/app/model"
+	"gxt-api-frame/app/model/entity"
+	iModel "gxt-api-frame/app/model/impl/model"
+	"gxt-api-frame/library/logger"
+	"time"
+
+	"github.com/gogf/gf/frame/g"
+	"go.uber.org/dig"
+	"gorm.io/driver/mysql"
+	"gorm.io/gorm"
+	"gorm.io/gorm/schema"
+)
+
+// 初始化gorm
+func initGorm() (*gorm.DB, error) {
+	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?%s",
+		g.Cfg().GetString("mysql.user"),
+		g.Cfg().GetString("mysql.password"),
+		g.Cfg().GetString("mysql.host"),
+		g.Cfg().GetInt("mysql.port"),
+		g.Cfg().GetString("mysql.db_name"),
+		g.Cfg().GetString("mysql.parameters"))
+
+	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
+		SkipDefaultTransaction: true, // 跳过默认事务
+		NamingStrategy: schema.NamingStrategy{
+			TablePrefix:   g.Cfg().GetString("gorm.table_prefix"),
+			SingularTable: true, // 使用单数表名
+		},
+	})
+	if err != nil {
+		return nil, err
+	}
+	db.Logger = logger.NewEntry(logger.GetLogger())
+	if g.Cfg().GetString("common.run_mode") == "debug" {
+		db.Debug()
+	}
+
+	sqlDb, err := db.DB()
+	if err != nil {
+		return nil, err
+	}
+	sqlDb.SetMaxIdleConns(g.Cfg().GetInt("gorm.max_idle_conns"))
+	sqlDb.SetMaxOpenConns(g.Cfg().GetInt("gorm.max_open_conns"))
+	sqlDb.SetConnMaxLifetime(time.Duration(g.Cfg().GetInt("gorm.max_open_conns")) * time.Second)
+	return db, nil
+}
+
+// 自动创建数据表映射
+func autoMigrate(db *gorm.DB) error {
+	return db.AutoMigrate(
+		new(entity.Demo),
+	)
+}
+
+// InjectModel
+func InjectModel(container *dig.Container) error {
+	_ = container.Provide(iModel.NewTrans)
+	_ = container.Provide(func(m *iModel.Trans) model.ITrans { return m })
+	_ = container.Provide(iModel.NewDemo)
+	_ = container.Provide(func(m *iModel.Demo) model.IDemo { return m })
+	return nil
+}

+ 104 - 0
config/config.toml

@@ -0,0 +1,104 @@
+# 通用配置
+[common]
+    # 运行模式(debug:开发,test:测试,release:正式)
+    run_mode = "debug"
+
+# HTTP Server
+[server]
+	Address     = ":8199"
+	ServerAgent = "gf-app"
+	LogPath     = "/tmp/log/gf-app/server"
+	# 请求读取超时时间
+	ReadTimeout = "60s"
+	# 客户端最大Body上传限制大小,默认为8*1024*1024=8MB
+    ClientMaxBodySize = 810241024
+    # 是否开启平滑重启特性,开启时将会在本地增加10000的本地TCP端口用于进程间通信。默认false
+    Graceful = false
+
+	# 静态服务配置
+	# 开关
+	FileServerEnable = false
+	# 静态文件目录
+	ServerRoot = ""
+	# 默认首页检索
+	IndexFiles = ["index.html"]
+	# PProf配置
+	# 是否开启PProf性能调试特性。默认为false
+    PProfEnabled = false
+    # 开启PProf时有效,表示PProf特性的页面访问路径,对当前Server绑定的所有域名有效。
+    PProfPattern = ""
+
+# Logger.
+[logger]
+    Path        = "/tmp/log/gf-app"
+    Level       = "all"
+    Stdout      = true
+    CtxKeys = ["user_id", "trace_id", "span_title", "span_function", "version"]
+# 请求频率限制(需要启用redis配置)
+[rate_limiter]
+    # 是否启用
+    enable = true
+    # 每分钟每个用户允许的最大请求数量
+    count = 10
+    # redis数据库(如果存储方式是redis,则指定存储的数据库)
+    redis_db = 10
+# 跨域请求
+[cors]
+    # 是否启用
+    enable = false
+    # 允许跨域请求的域名列表(*表示全部允许)
+    allow_origins = ["*"]
+    # 允许跨域请求的请求方式列表
+    allow_methods = ["GET","POST","PUT","DELETE","PATCH"]
+    # 允许客户端与跨域请求一起使用的非简单标头的列表
+    allow_headers = []
+    # 请求是否可以包含cookie,HTTP身份验证或客户端SSL证书等用户凭据
+    allow_credentials = true
+    # 可以缓存预检请求结果的时间(以秒为单位)
+    max_age = 7200
+# redis配置
+[redis]
+    # 开关
+    enable = false
+    # 地址
+    addr = "127.0.0.1:6379"
+    # 密码
+    password = ""
+    # 默认库
+    db = 14
+# mysql数据库配置
+[mysql]
+    # 连接地址
+    host = "39.98.250.155"
+    # 连接端口
+    port= 3306
+    # 用户名
+    user = "root"
+    # 密码
+    password = "gEkYDPloQcp93t4WHr3X"
+    # 数据库
+    db_name = "gxt-release"
+    # 连接参数
+    parameters = "charset=utf8mb4&parseTime=True&loc=Local&allowNativePasswords=true"
+# gorm配置
+[gorm]
+    # 设置连接可以重用的最长时间(单位:秒)
+    max_lifetime = 7200
+    # 设置数据库的最大打开连接数
+    max_open_conns = 150
+    # 设置空闲连接池中的最大连接数
+    max_idle_conns = 50
+    # 数据库表名前缀
+    table_prefix = "m_"
+    # 是否启用自动映射数据库表结构
+    enable_auto_migrate = true
+    # 慢查询阀值(单位:毫秒)
+    slow_sql_limit = 2000
+# jwt 认证配置
+[jwt]
+#加密Key
+signing_key = "gxt-api-frame"
+# 签名加密方式(HS256, HS384, HS512)
+signing_method = "HS512"
+# 过期时间,秒
+expired = 7200

+ 0 - 0
docker/.gitkeep


+ 0 - 0
document/.gitkeep


+ 13 - 0
go.mod

@@ -0,0 +1,13 @@
+module gxt-api-frame
+
+require (
+	github.com/dgrijalva/jwt-go v3.2.0+incompatible
+	github.com/go-redis/redis/v8 v8.3.3
+	github.com/go-redis/redis_rate/v9 v9.0.2
+	github.com/gogf/gf v1.14.2
+	go.uber.org/dig v1.10.0
+	gorm.io/driver/mysql v1.0.3
+	gorm.io/gorm v1.20.6
+)
+
+go 1.15

+ 168 - 0
library/auth/jwt_auth.go

@@ -0,0 +1,168 @@
+package auth
+
+import (
+	"encoding/json"
+	"github.com/dgrijalva/jwt-go"
+	"gxt-api-frame/app/errors"
+	"time"
+)
+
+// TokenInfo 令牌信息
+type TokenInfo interface {
+	// 获取访问令牌
+	GetAccessToken() string
+	// 获取令牌类型
+	GetTokenType() string
+	// 获取令牌到期时间戳
+	GetExpiresAt() int64
+	// JSON编码
+	EncodeToJSON() ([]byte, error)
+}
+
+// tokenInfo 令牌信息
+type tokenInfo struct {
+	AccessToken string `json:"access_token"` // 访问令牌
+	TokenType   string `json:"token_type"`   // 令牌类型
+	ExpiresAt   int64  `json:"expires_at"`   // 令牌到期时间
+}
+
+func (t *tokenInfo) GetAccessToken() string {
+	return t.AccessToken
+}
+
+func (t *tokenInfo) GetTokenType() string {
+	return t.TokenType
+}
+
+func (t *tokenInfo) GetExpiresAt() int64 {
+	return t.ExpiresAt
+}
+
+func (t *tokenInfo) EncodeToJSON() ([]byte, error) {
+	return json.Marshal(t)
+}
+
+type options struct {
+	signingMethod jwt.SigningMethod
+	signingKey    interface{}
+	keyfunc       jwt.Keyfunc
+	expired       int
+	tokenType     string
+}
+
+const defaultKey = "smartpark"
+
+var defaultOptions = options{
+	tokenType:     "Bearer",
+	expired:       7200,
+	signingMethod: jwt.SigningMethodHS512,
+	signingKey:    []byte(defaultKey),
+	keyfunc: func(t *jwt.Token) (interface{}, error) {
+		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
+			return nil, errors.ErrInvalidToken
+		}
+		return []byte(defaultKey), nil
+	},
+}
+
+// SetSigningMethod 设定签名方式
+func SetSigningMethod(method jwt.SigningMethod) Option {
+	return func(o *options) {
+		o.signingMethod = method
+	}
+}
+
+// SetSigningKey 设定签名key
+func SetSigningKey(key interface{}) Option {
+	return func(o *options) {
+		o.signingKey = key
+	}
+}
+
+// SetKeyfunc 设定验证key的回调函数
+func SetKeyfunc(keyFunc jwt.Keyfunc) Option {
+	return func(o *options) {
+		o.keyfunc = keyFunc
+	}
+}
+
+// SetExpired 设定令牌过期时长(单位秒,默认7200)
+func SetExpired(expired int) Option {
+	return func(o *options) {
+		o.expired = expired
+	}
+}
+
+type Option func(*options)
+
+type JWTAuth struct {
+	opts *options
+}
+
+func New(opts ...Option) *JWTAuth {
+	o := defaultOptions
+	for _, opt := range opts {
+		opt(&o)
+	}
+	return &JWTAuth{opts: &o}
+}
+
+// GenerateToken 生成令牌
+func (a *JWTAuth) GenerateToken(userID string) (TokenInfo, error) {
+	now := time.Now()
+	expiresAt := now.Add(time.Duration(a.opts.expired) * time.Second).Unix()
+	token := jwt.NewWithClaims(a.opts.signingMethod, &jwt.StandardClaims{
+		IssuedAt:  now.Unix(),
+		ExpiresAt: expiresAt,
+		NotBefore: now.Unix(),
+		Subject:   userID,
+	})
+
+	tokenString, err := token.SignedString(a.opts.signingKey)
+	if err != nil {
+		return nil, err
+	}
+
+	tokenInfo := &tokenInfo{
+		ExpiresAt:   expiresAt,
+		TokenType:   a.opts.tokenType,
+		AccessToken: tokenString,
+	}
+	return tokenInfo, nil
+}
+
+// 解析令牌
+func (a *JWTAuth) parseToken(tokenString string) (*jwt.StandardClaims, error) {
+	token, _ := jwt.ParseWithClaims(tokenString, &jwt.StandardClaims{}, a.opts.keyfunc)
+	if !token.Valid {
+		return nil, errors.ErrInvalidToken
+	}
+
+	return token.Claims.(*jwt.StandardClaims), nil
+}
+
+// ParseUserID 解析用户ID
+func (a *JWTAuth) ParseUserID(tokenString string) (string, error) {
+	claims, err := a.parseToken(tokenString)
+	if err != nil {
+		return "", err
+	}
+
+	return claims.Subject, nil
+}
+// Auther 认证接口
+type Auther interface {
+	// 生成令牌
+	GenerateToken(userID string) (TokenInfo, error)
+
+	// 销毁令牌
+	//DestroyToken(accessToken string) error
+
+	// 解析用户ID
+	ParseUserID(accessToken string) (string, error)
+
+	// 释放资源
+	//Release() error
+
+	//GenerateApiToken(userID string) (TokenInfo, error)
+}

+ 182 - 0
library/gplus/gplus.go

@@ -0,0 +1,182 @@
+package gplus
+
+import (
+	"context"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	iContext "gxt-api-frame/app/context"
+	"gxt-api-frame/app/errors"
+	"gxt-api-frame/app/schema"
+	"gxt-api-frame/library/logger"
+	"gxt-api-frame/library/utils"
+	"net/http"
+	"strings"
+)
+
+// 定义上下文中的键
+const (
+	prefix = "gao-xin-tong"
+	// UserIDKey 存储上下文中的键(用户ID)
+	UserIDKey = prefix + "/user-id"
+	// UserTypeKey 存储上下文中的键(用户类型)
+	UserTypeKey = prefix + "/user-type"
+	// TraceIDKey 存储上下文中的键(跟踪ID)
+	TraceIDKey = prefix + "/trace-id"
+	// ResBodyKey 存储上下文中的键(响应Body数据)
+	ResBodyKey = prefix + "/res-body"
+)
+
+// ParseJson 解析请求参数Json
+func ParseJson(r *ghttp.Request, out interface{}) error {
+	if err := r.Parse(out); err != nil {
+		m := "解析请求参数发生错误"
+		if g.Cfg().GetString("common.run_mode") == "debug" {
+			m += "[" + err.Error() + "]"
+		}
+		return errors.Wrap400Response(err, m)
+	}
+	return nil
+}
+
+// GetPageIndex 获取当前页
+func GetPageIndex(r *ghttp.Request) int {
+	defaultVal := 1
+	if v := r.GetQueryInt("current"); v > 0 {
+		return v
+	}
+	return defaultVal
+}
+
+// GetPageSize 获取分页的页大小(最大50)
+func GetPageSize(r *ghttp.Request) int {
+	defaultVal := 10
+	if v := r.GetQueryInt("pageSize"); v > 0 {
+		if v > 50 {
+			v = 50
+		}
+		return v
+	}
+	return defaultVal
+}
+
+// ResPage 分页响应
+func ResPage(r *ghttp.Request, v interface{}, pr *schema.PaginationResult) {
+	result := schema.HTTPList{
+		List: v,
+		Pagination: &schema.HTTPPagination{
+			Current:  GetPageIndex(r),
+			PageSize: GetPageSize(r),
+		},
+	}
+	if pr != nil {
+		result.Pagination.Total = pr.Total
+	}
+	ResSuccess(r, result)
+}
+
+// ResSuccess 响应成功
+func ResSuccess(c *ghttp.Request, v interface{}) {
+	ResJSON(c, http.StatusOK, v)
+}
+
+// ResOK 响应OK
+func ResOK(c *ghttp.Request) {
+	ResSuccess(c, schema.HTTPStatus{Status: "OK"})
+}
+
+// ResList 响应列表数据
+func ResList(c *ghttp.Request, v interface{}) {
+	ResSuccess(c, schema.HTTPList{List: v})
+}
+
+// ResJSON 响应JSON结果
+func ResJSON(r *ghttp.Request, code int, v interface{}) {
+	errors.JsonExit(r, code, v)
+}
+
+// ResError 响应错误
+func ResError(r *ghttp.Request, err error, status ...int) {
+	var res *errors.ResponseError
+	if err != nil {
+		if e, ok := err.(*errors.ResponseError); ok {
+			res = e
+		} else {
+			res = errors.UnWrapResponse(errors.Wrap500Response(err))
+		}
+	} else {
+
+		res = errors.UnWrapResponse(errors.ErrInternalServer)
+	}
+
+	if len(status) > 0 {
+		res.StatusCode = status[0]
+	}
+
+	if err := res.ERR; err != nil {
+		if res.StatusCode >= 500 {
+			logger.Errorf(NewContext(r), "%s", err)
+		}
+	}
+
+	eitem := schema.HTTPErrorItem{
+		Code:    res.Code,
+		Message: res.Message,
+		TraceId: GetTraceID(r),
+	}
+	ResJSON(r, res.StatusCode, schema.HTTPError{Error: eitem})
+}
+
+// SetUserId 上下文中设置用户Id
+func SetUserId(r *ghttp.Request, userId string) {
+	r.SetCtxVar(UserIDKey, userId)
+}
+
+// GetUserId 获取上下文中的用户Id
+func GetUserId(r *ghttp.Request) string {
+	return r.GetCtxVar(UserIDKey).String()
+}
+
+// GetToken 获取token
+func GetToken(r *ghttp.Request) string {
+	var token string
+	auth := r.GetHeader("Authorization")
+	prefix := "Bearer "
+	if auth != "" && strings.HasPrefix(auth, prefix) {
+		token = auth[len(prefix):]
+	}
+	return token
+}
+
+// NewContext 封装上下文入口
+func NewContext(r *ghttp.Request) context.Context {
+	parent := context.Background()
+
+	traceId := GetTraceID(r)
+	if traceId == "" {
+		traceId = utils.NewTraceID()
+	}
+	parent = iContext.NewTraceID(parent, traceId)
+	parent = logger.NewTraceIDContext(parent, traceId)
+
+	if v := GetUserID(r); v != "" {
+		parent = iContext.NewUserID(parent, v)
+		parent = logger.NewUserIDContext(parent, v)
+	}
+
+	return parent
+}
+
+// GetTraceID 获取追踪ID
+func GetTraceID(c *ghttp.Request) string {
+	return c.GetCtxVar(TraceIDKey).String()
+}
+
+// GetUserID 获取用户ID
+func GetUserID(c *ghttp.Request) string {
+	return c.GetCtxVar(UserIDKey).String()
+}
+
+// GetUserType 获取用户类型
+func GetUserType(c *ghttp.Request) string {
+	return c.GetCtxVar(UserTypeKey).String()
+}

+ 64 - 0
library/logger/gorm_log.go

@@ -0,0 +1,64 @@
+package logger
+
+import (
+	"context"
+	"fmt"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/glog"
+	"gorm.io/gorm/logger"
+	"gorm.io/gorm/utils"
+	"time"
+)
+
+func (e *Entry) LogMode(lv logger.LogLevel) logger.Interface {
+	switch lv {
+	case logger.Error:
+		e.entry.SetLevel(e.entry.GetLevel() | glog.LEVEL_ERRO)
+		break
+	case logger.Info:
+		e.entry.SetLevel(e.entry.GetLevel() | glog.LEVEL_INFO)
+		break
+	}
+	return e
+}
+
+func (e *Entry) Info(ctx context.Context, format string, args ...interface{}) {
+	e.entry.Ctx(ctx).Infof(format, args...)
+}
+
+func (e *Entry) Warn(ctx context.Context, format string, args ...interface{}) {
+	e.entry.Ctx(ctx).Warningf(format, args...)
+}
+
+func (e *Entry) Error(ctx context.Context, format string, args ...interface{}) {
+	e.entry.Ctx(ctx).Errorf(format, args...)
+}
+
+func (e *Entry) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
+	limit := g.Cfg().GetInt("gorm.slow_sql_limit")
+	if e.entry.GetLevel() > 0 {
+		elapsed := time.Since(begin)
+		switch {
+		case err != nil && e.entry.GetLevel() >= glog.LEVEL_ERRO:
+			{
+				sql, _ := fc()
+				e.Info(ctx, "%s\nSQL查询出错:%s\n执行SQL:%s", utils.FileWithLineNum(), err, sql)
+			}
+		case elapsed > time.Duration(limit)*time.Millisecond && e.entry.GetLevel() >= glog.LEVEL_WARN:
+			sql, rows := fc()
+			slowLog := fmt.Sprintf("执行时间 %v", elapsed)
+			if rows == -1 {
+				e.Warnf("%s\n慢查询SQL:%s\n%s \n影响行数:[%s]", utils.FileWithLineNum(), sql, slowLog, "-")
+			} else {
+				e.Warnf("%s\n慢查询SQL:%s\n%s\n影响行数:[%d]", utils.FileWithLineNum(), sql, slowLog, rows)
+			}
+		case e.entry.GetLevel() >= glog.LEVEL_INFO:
+			sql, rows := fc()
+			if rows == -1 {
+				e.Infof("执行SQL:[%s],影响行数:[%s]", sql, "-")
+			} else {
+				e.Infof("执行SQL:[%s], 影响行数:[%d]", sql, rows)
+			}
+		}
+	}
+}

+ 199 - 0
library/logger/logger.go

@@ -0,0 +1,199 @@
+package logger
+
+import (
+	"context"
+	"errors"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/os/glog"
+)
+
+// 定义键名
+const (
+	TraceIDKey      = "trace_id"
+	UserIDKey       = "user_id"
+	SpanTitleKey    = "span_title"
+	SpanFunctionKey = "span_function"
+	VersionKey      = "version"
+)
+
+var log *glog.Logger
+
+// TraceIDFunc 定义获取跟踪ID的函数
+type TraceIDFunc func() string
+
+var (
+	version     string
+	traceIDFunc TraceIDFunc
+)
+
+// SetTraceIdFunc 设置获取追踪Id的生成函数
+func SetTraceIdFunc(fn TraceIDFunc) {
+	traceIDFunc = fn
+}
+
+// SetVersion 设置版本号
+func SetVersion(ver string) {
+	version = ver
+}
+
+// FromTraceIDContext 从上下文中获取跟踪ID
+func FromTraceIDContext(ctx context.Context) string {
+	v := ctx.Value(TraceIDKey)
+	if v != nil {
+		if s, ok := v.(string); ok {
+			return s
+		}
+	}
+	return getTraceID()
+}
+
+// NewUserIDContext 创建用户ID上下文
+func NewUserIDContext(ctx context.Context, userID string) context.Context {
+	return context.WithValue(ctx, UserIDKey, userID)
+}
+
+// FromUserIDContext 从上下文中获取用户ID
+func FromUserIDContext(ctx context.Context) string {
+	v := ctx.Value(UserIDKey)
+	if v != nil {
+		if s, ok := v.(string); ok {
+			return s
+		}
+	}
+	return ""
+}
+
+func getTraceID() string {
+	if traceIDFunc != nil {
+		return traceIDFunc()
+	}
+	return ""
+}
+
+// GetLogger 获取logger
+func GetLogger() *glog.Logger {
+	return log
+}
+
+// NewTraceIDContext 创建跟踪ID上下文
+func NewTraceIDContext(ctx context.Context, traceID string) context.Context {
+	return context.WithValue(ctx, TraceIDKey, traceID)
+}
+
+// Init 初始化日志配置
+func Init(runMode string) {
+	if log != nil {
+		panic(errors.New("重复初始化logger"))
+	}
+	log = g.Log()
+	var lv string
+	switch runMode {
+	case "debug":
+		lv = "DEV"
+		break
+	case "test":
+		lv = "DEV"
+		break
+	case "release":
+		lv = "PRODUCT"
+		break
+	}
+	err := log.SetLevelStr(lv)
+	if err != nil {
+		panic(err)
+	}
+}
+
+type spanOptions struct {
+	Title    string
+	FuncName string
+}
+
+// SpanOption 定义跟踪单元的数据项
+type SpanOption func(*spanOptions)
+
+// SetSpanTitle 设置跟踪单元的标题
+func SetSpanTitle(title string) SpanOption {
+	return func(o *spanOptions) {
+		o.Title = title
+	}
+}
+
+// SetSpanFuncName 设置跟踪单元的函数名
+func SetSpanFuncName(funcName string) SpanOption {
+	return func(o *spanOptions) {
+		o.FuncName = funcName
+	}
+}
+
+// StartSpan 开始一个追踪单元
+func StartSpan(ctx context.Context, opts ...SpanOption) *Entry {
+	if ctx == nil {
+		ctx = context.Background()
+	}
+
+	var o spanOptions
+	for _, opt := range opts {
+		opt(&o)
+	}
+	return NewEntry(log)
+}
+
+// Entry 定义统一的日志写入方式
+type Entry struct {
+	entry *glog.Logger
+}
+
+func NewEntry(entry *glog.Logger) *Entry {
+	return &Entry{entry: entry}
+}
+
+// Debugf 写入调试日志
+func Debugf(ctx context.Context, format string, args ...interface{}) {
+	StartSpan(ctx).entry.Ctx(ctx).Debugf(format, args)
+}
+
+// Printf 写入消息日志
+func Printf(ctx context.Context, format string, args ...interface{}) {
+	StartSpan(ctx).entry.Ctx(ctx).Printf(format, args...)
+}
+
+// Warnf 写入警告日志
+func Warnf(ctx context.Context, format string, args ...interface{}) {
+	StartSpan(ctx).entry.Ctx(ctx).Warningf(format, args...)
+}
+
+// Fatalf 写入重大错误日志
+func Fatalf(ctx context.Context, format string, args ...interface{}) {
+	StartSpan(ctx).entry.Ctx(ctx).Fatalf(format, args...)
+}
+
+// Errorf 错误日志
+func Errorf(ctx context.Context, format string, args ...interface{}) {
+	StartSpan(ctx).entry.Ctx(ctx).Errorf(format, args...)
+}
+
+// Errorf 错误日志
+func (e *Entry) Errorf(format string, args ...interface{}) {
+	e.entry.Errorf(format, args...)
+}
+
+// Warnf 警告日志
+func (e *Entry) Warnf(format string, args ...interface{}) {
+	e.entry.Warningf(format, args...)
+}
+
+// Infof 消息日志
+func (e *Entry) Infof(format string, args ...interface{}) {
+	e.entry.Infof(format, args...)
+}
+
+// Printf 消息日志
+func (e *Entry) Printf(format string, args ...interface{}) {
+	e.entry.Printf(format, args...)
+}
+
+// Debugf 写入调试日志
+func (e *Entry) Debugf(format string, args ...interface{}) {
+	e.entry.Debugf(format, args...)
+}

+ 48 - 0
library/middleware/middleware.go

@@ -0,0 +1,48 @@
+package middleware
+
+import "github.com/gogf/gf/net/ghttp"
+
+// EmptyMiddleware 不执行业务处理的中间件
+func EmptyMiddleware(r *ghttp.Request) {
+	r.Middleware.Next()
+}
+
+type SkipperFunc func(request *ghttp.Request) bool
+// AllowPathPrefixSkipper 检查请求路径是否包含指定的前缀,如果包含则跳过
+func AllowPathPrefixSkipper(prefixes ...string) SkipperFunc {
+	return func(request *ghttp.Request) bool {
+		path := request.URL.Path
+		pathLen := len(path)
+		for _, p := range prefixes {
+			if pl := len(p);pathLen >=pl && path[:pl] == p {
+				return true
+			}
+		}
+		return false
+	}
+}
+
+// AllowPathPrefixNoSkipper 检查请求路径是否包含指定的前缀,如果包含则不跳过
+func AllowPathPrefixNoSkipper(prefixes ...string) SkipperFunc {
+	return func(request *ghttp.Request) bool {
+		path := request.URL.Path
+		pathLen := len(path)
+
+		for _, p := range prefixes {
+			if pl := len(p); pathLen >= pl && path[:pl] == p {
+				return false
+			}
+		}
+		return true
+	}
+}
+
+// SkipHandler 统一处理跳过函数
+func SkipHandler(r *ghttp.Request, skippers ...SkipperFunc) bool {
+	for _, skipper := range skippers {
+		if skipper(r) {
+			return true
+		}
+	}
+	return false
+}

+ 39 - 0
library/middleware/mw_auth.go

@@ -0,0 +1,39 @@
+package middleware
+
+import (
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"gxt-api-frame/app/errors"
+	"gxt-api-frame/library/auth"
+	"gxt-api-frame/library/gplus"
+)
+
+func UserAuthMiddleware(skippers ...SkipperFunc) ghttp.HandlerFunc {
+	jwt := auth.New()
+	return func(r *ghttp.Request) {
+		if len(skippers) >0 && skippers[0](r) {
+			r.Middleware.Next()
+			return
+		}
+		var userId string
+		if t := gplus.GetToken(r); t != "" {
+			id, err := jwt.ParseUserID(t)
+			if err != nil {
+				gplus.ResError(r, err)
+			}
+			userId = id
+		}
+		if userId != "" {
+			gplus.SetUserId(r, userId)
+		}
+		if userId == "" {
+			if g.Cfg().GetString("common.RunMode") == "debug" {
+				gplus.SetUserId(r, g.Cfg().GetString("root.user_name"))
+				r.Middleware.Next()
+				return
+			}
+			gplus.ResError(r, errors.ErrNoPerm)
+		}
+		r.Middleware.Next()
+	}
+}

+ 71 - 0
library/middleware/mw_rate_limiter.go

@@ -0,0 +1,71 @@
+package middleware
+
+import (
+	"github.com/go-redis/redis/v8"
+	"github.com/go-redis/redis_rate/v9"
+	"github.com/gogf/gf/frame/g"
+	"github.com/gogf/gf/net/ghttp"
+	"gxt-api-frame/app/errors"
+	"gxt-api-frame/library/gplus"
+	"gxt-api-frame/library/logger"
+	"strconv"
+)
+
+// RateLimiterMiddleware 请求频率限制中间件
+func RateLimiterMiddleware(skippers ...SkipperFunc) ghttp.HandlerFunc {
+	if !g.Cfg().GetBool("rate_limiter.enable") {
+		return EmptyMiddleware
+	}
+	// check enable redis
+	if !g.Cfg().GetBool("redis.enable") {
+		return func(r *ghttp.Request) {
+			logger.Warnf(gplus.NewContext(r), "限流中间件无法正常使用,请启用redis配置[redis.enable]")
+			r.Middleware.Next()
+		}
+	}
+
+	addr := g.Cfg().GetString("redis.addr")
+	password := g.Cfg().GetString("redis.password")
+	db := g.Cfg().GetInt("redis.db")
+	ring := redis.NewRing(&redis.RingOptions{
+		Addrs: map[string]string{
+			"server1": addr,
+		},
+		Password: password,
+		DB:       db,
+	})
+
+	limiter := redis_rate.NewLimiter(ring)
+
+	return func(r *ghttp.Request) {
+		if SkipHandler(r, skippers...) {
+			r.Middleware.Next()
+			return
+		}
+
+		userID := gplus.GetUserID(r)
+		if userID == "" {
+			r.Middleware.Next()
+			return
+		}
+		ctx := gplus.NewContext(r)
+		limit := g.Cfg().GetInt("rate_limiter.count")
+		result, err := limiter.Allow(ctx,
+			userID, redis_rate.PerMinute(limit))
+		if err != nil {
+			gplus.ResError(r, errors.ErrInternalServer)
+		}
+		if result != nil {
+			if result.Allowed == 0 {
+				h := r.Response.Header()
+				h.Set("X-RateLimit-Limit", strconv.FormatInt(int64(result.Limit.Burst), 10))
+				h.Set("X-RateLimit-Remaining", strconv.FormatInt(int64(result.Remaining), 10))
+				h.Set("X-RateLimit-Reset", strconv.FormatInt(int64(result.ResetAfter.Seconds()), 10))
+				gplus.ResError(r, errors.ErrTooManyRequests)
+				return
+			}
+		}
+
+		r.Middleware.Next()
+	}
+}

+ 20 - 0
library/middleware/mw_trace.go

@@ -0,0 +1,20 @@
+package middleware
+
+import (
+	"github.com/gogf/gf/net/ghttp"
+	"gxt-api-frame/library/gplus"
+	"gxt-api-frame/library/utils"
+)
+
+func TraceIdMiddleware(skippers ...SkipperFunc) ghttp.HandlerFunc {
+	return func(r *ghttp.Request) {
+		if len(skippers) > 0 && skippers[0](r) {
+			r.Middleware.Next()
+			return
+		}
+		if r.GetCtxVar(gplus.TraceIDKey).String() == "" {
+			r.SetCtxVar(gplus.TraceIDKey, utils.NewTraceID())
+		}
+		r.Middleware.Next()
+	}
+}

+ 36 - 0
library/redis/redis.go

@@ -0,0 +1,36 @@
+package redis
+
+import (
+	"context"
+	"github.com/go-redis/redis/v8"
+	"sync"
+)
+
+var (
+	once sync.Once
+)
+
+// Init 初始化redis客户端
+func Init(ctx context.Context, addr, password string, db int) *redis.Client {
+	var internalClient *redis.Client
+	once.Do(func() {
+		internalClient = newCli(ctx, addr, password, db)
+	})
+	return internalClient
+}
+
+// New 创建redis客户端实例
+func newCli(ctx context.Context, addr, password string, db int) *redis.Client {
+	cli := redis.NewClient(&redis.Options{
+		Addr:     addr,
+		Password: password,
+		DB:       db,
+	})
+
+	cmd := cli.Ping(ctx)
+	if err := cmd.Err(); err != nil {
+		panic(err)
+	}
+
+	return cli
+}

+ 39 - 0
library/utils/util.go

@@ -0,0 +1,39 @@
+package utils
+
+import (
+	"fmt"
+	"os"
+	"regexp"
+	"runtime"
+	"strconv"
+	"strings"
+	"time"
+)
+
+var (
+	pid           = os.Getpid()
+	gormSourceDir string
+)
+
+func init() {
+	_, file, _, _ := runtime.Caller(0)
+	gormSourceDir = regexp.MustCompile(`utils.utils\.go`).ReplaceAllString(file, "")
+}
+
+// NewTraceID 创建追踪ID
+func NewTraceID() string {
+	return fmt.Sprintf("trace-id-%d-%s",
+		pid,
+		time.Now().Format("2006.01.02.15.04.05.999999"))
+}
+
+func FileWithLineNum() string {
+	for i := 2; i < 15; i++ {
+		_, file, line, ok := runtime.Caller(i)
+
+		if ok && (!strings.HasPrefix(file, gormSourceDir) || strings.HasSuffix(file, "_test.go")) {
+			return file + ":" + strconv.FormatInt(int64(line), 10)
+		}
+	}
+	return ""
+}

+ 11 - 0
main.go

@@ -0,0 +1,11 @@
+package main
+
+import (
+	"github.com/gogf/gf/frame/g"
+	_ "gxt-api-frame/boot"
+	_ "gxt-api-frame/router"
+)
+
+func main() {
+	g.Server().Run()
+}

+ 0 - 0
public/html/.gitkeep


+ 0 - 0
public/plugin/.gitkeep


+ 0 - 0
public/resource/css/.gitkeep


+ 0 - 0
public/resource/image/.gitkeep


+ 0 - 0
public/resource/js/.gitkeep


+ 27 - 0
router/api/api.go

@@ -0,0 +1,27 @@
+package api
+
+import (
+	"github.com/gogf/gf/net/ghttp"
+	"go.uber.org/dig"
+	"gxt-api-frame/library/middleware"
+	"gxt-api-frame/router/api/controllers"
+)
+
+// 注册路由
+func RegisterRouters(s *ghttp.Server, container *dig.Container) error {
+	controllers.Inject(container)
+	gr := s.Group("/api")
+	// 注册请求限制中间件
+	gr.Middleware(middleware.RateLimiterMiddleware())
+	return container.Invoke(func(
+		cDemo *controllers.Demo,
+	) {
+		v1 := gr.Group("/v1")
+		{
+			gDemo := v1.Group("/demos")
+			{
+				gDemo.POST("/", cDemo.Create)
+			}
+		}
+	})
+}

+ 41 - 0
router/api/controllers/c_demo.go

@@ -0,0 +1,41 @@
+package controllers
+
+import (
+	"github.com/gogf/gf/net/ghttp"
+	"gxt-api-frame/app/bll"
+	"gxt-api-frame/app/schema"
+	"gxt-api-frame/library/gplus"
+)
+
+type Demo struct {
+	cBll bll.IDemo
+}
+
+func NewDemo(cb bll.IDemo) *Demo {
+	return &Demo{
+		cBll: cb,
+	}
+}
+
+// Create 创建数据
+// @Tags API-Demo
+// @Summary 创建数据
+// @Param body body schema.Demo true "提交的数据"
+// @Success 200 {object} schema.Demo
+// @Failure 400 {object} schema.HTTPError "{error:{code:0,message:无效的请求参数}}"
+// @Failure 401 {object} schema.HTTPError "{error:{code:0,message:未授权}}"
+// @Failure 500 {object} schema.HTTPError "{error:{code:0,message:服务器错误}}"
+// @Router /api/v1/demos/ [post]
+func (a *Demo) Create(r *ghttp.Request) {
+	var data schema.Demo
+	if err := gplus.ParseJson(r, &data); err != nil {
+		gplus.ResError(r, err)
+	}
+
+	ctx := gplus.NewContext(r)
+	result, err := a.cBll.Create(ctx, data)
+	if err != nil {
+		gplus.ResError(r, err)
+	}
+	gplus.ResSuccess(r, result)
+}

+ 8 - 0
router/api/controllers/ctl.go

@@ -0,0 +1,8 @@
+package controllers
+
+import "go.uber.org/dig"
+
+// 注入 controllers
+func Inject(container *dig.Container) {
+	_ = container.Provide(NewDemo)
+}

+ 15 - 0
router/router.go

@@ -0,0 +1,15 @@
+package router
+
+import (
+	"github.com/gogf/gf/net/ghttp"
+	"go.uber.org/dig"
+	"gxt-api-frame/router/api"
+)
+// InitRouters 初始化路由注册
+func InitRouters(s *ghttp.Server, container *dig.Container) {
+	// 注册api路由组
+	err := api.RegisterRouters(s, container)
+	if err != nil {
+		panic(err)
+	}
+}

+ 18 - 0
router/swagger.go

@@ -0,0 +1,18 @@
+/*
+Package routers 生成swagger文档
+
+文档规则请参考:https://github.com/swaggo/swag#declarative-comments-format
+
+使用方式:
+
+	go get -u github.com/swaggo/swag/cmd/swag
+	swag init -g ./internal/app/routers/swagger.go -o ./docs/swagger*/
+package router
+
+// @title 高新通后台开发框架
+// @version 1.0.0
+// @description 高新通内部开发框架,基于gf+gorm+dig。
+// @schemes http https
+// @host 39.98.250.155:10076
+// host 127.0.0.1:10076
+// @basePath /

+ 116 - 0
swagger/swagger.json

@@ -0,0 +1,116 @@
+{
+    "swagger": "2.0",
+    "info": {
+        "contact": {},
+        "license": {}
+    },
+    "paths": {
+        "/api/v1/demos/": {
+            "post": {
+                "tags": [
+                    "API-Demo"
+                ],
+                "summary": "创建数据",
+                "parameters": [
+                    {
+                        "description": "提交的数据",
+                        "name": "body",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/schema.Demo"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/schema.Demo"
+                        }
+                    },
+                    "400": {
+                        "description": "{error:{code:0,message:无效的请求参数}}",
+                        "schema": {
+                            "$ref": "#/definitions/schema.HTTPError"
+                        }
+                    },
+                    "401": {
+                        "description": "{error:{code:0,message:未授权}}",
+                        "schema": {
+                            "$ref": "#/definitions/schema.HTTPError"
+                        }
+                    },
+                    "500": {
+                        "description": "{error:{code:0,message:服务器错误}}",
+                        "schema": {
+                            "$ref": "#/definitions/schema.HTTPError"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "definitions": {
+        "schema.Demo": {
+            "type": "object",
+            "properties": {
+                "code": {
+                    "description": "编号",
+                    "type": "string"
+                },
+                "created_at": {
+                    "description": "创建时间",
+                    "type": "string"
+                },
+                "creator": {
+                    "description": "创建者",
+                    "type": "string"
+                },
+                "memo": {
+                    "description": "备注",
+                    "type": "string"
+                },
+                "name": {
+                    "description": "名称",
+                    "type": "string"
+                },
+                "record_id": {
+                    "description": "记录ID",
+                    "type": "string"
+                },
+                "status": {
+                    "description": "状态(1:启用 2:停用)",
+                    "type": "integer"
+                }
+            }
+        },
+        "schema.HTTPError": {
+            "type": "object",
+            "properties": {
+                "error": {
+                    "description": "错误项",
+                    "type": "object",
+                    "$ref": "#/definitions/schema.HTTPErrorItem"
+                }
+            }
+        },
+        "schema.HTTPErrorItem": {
+            "type": "object",
+            "properties": {
+                "code": {
+                    "description": "错误码",
+                    "type": "integer"
+                },
+                "message": {
+                    "description": "错误信息",
+                    "type": "string"
+                },
+                "trace_id": {
+                    "description": "追踪Id,用于快速定位错误",
+                    "type": "string"
+                }
+            }
+        }
+    }
+}

+ 0 - 0
template/.gitkeep