GoFrame Step by Step Demo P1

  • 框架说明文档
  • GFTool 安装
  • Web框架学习

文章目录

  • GoFrame Step by Step Demo P1
    • 参考Demo
    • 记录
      • 安装GF Tool加入环境变量
      • 创建并初始化项目
      • 配置项目数据
      • 数据表构建
      • 路由
        • 1. API
        • 2. 鉴权
        • 3. 用户
        • 4. 权限
        • Code
        • 验证码
    • 本片后记

参考Demo

StandAlone Projects using GoFrame

  • gf-demos - (Official) GoFrame新手入门基础演示项目。
  • gf-cli - (Official) GoFrame Command Line Interface.
  • focus-single - (Official) GoFrame开源社区项目,可作为一个完整项目示例。
  • gfast - 基于GoFrame框架的后台管理系统。
  • gmanager - 基于GoFrame框架的管理平台。
  • gf-vue-admin - 基于GoFrame+Vue搭建的后台管理系统框架。
  • gf-admin-api - 一个前后端分离项目,前端Vue.js、后端GoFrame。
  • gea - 基于GoFrame、Vue & Element的前后端分离权限管理系统。
  • dmicro - 基于GoFrame的rpc框架。

Libraries and Plugins using GoFrame

  • polaris - Polaris with GoFrame.
  • gtoken - 基于GoFrame框架的token插件,通过服务端验证方式实现token认证.
  • gf-jwt - GoFrame HTTP JWT middleware.
  • csrf - CSRF middleware for GoFrame web server.
  • gdb-adapter - GoFrame ORM adapter for Casbin.

Use Cases using GoFrame

  • Bingo - 个人博客程序,后台前端采用Vue2+ElementUI2,服务端用的GoFrame。
  • 云传意 - 微信小程序:云传意。

记录

安装GF Tool加入环境变量

USAGEgf COMMAND [ARGUMENT] [OPTION]COMMANDenv        show current Golang environment variablesget        install or update GF to system in default...gen        automatically generate go files for ORM models...mod        extra features for go modules...run        running go codes with hot-compiled-like feature...init       create and initialize an empty GF project...help       show more information about a specified commandpack       packing any file/directory to a resource file, or a go file...build      cross-building go project for lots of platforms...docker     create a docker image for current GF project...swagger    swagger feature for current project...update     update current gf binary to latest one (might need root/admin permission)install    install gf binary to system (might need root/admin permission)version    show current binary version infoOPTION-y         all yes for all command without prompt ask-?,-h      show this help or detail for specified command-v,-i      show version information-debug     show internal detailed debugging informationADDITIONALUse 'gf help COMMAND' or 'gf COMMAND -h' for detail about a command, which has '...'in the tail of their comments.

创建并初始化项目

  • 使用gf init quabg创建项目
  • cd quabg 然后go mod download下载所有依赖库
  • gf run main.go启动服务(包含自动热重启功能)

然后就可以使用VS Code或者Idea开发你的代码啦!

Note: 如果go下载过慢,需要设置代理。参考goproxy

项目结构如下:(注意版本,新版V2与V1在目录结构上有差别)

│  Dockerfile
│  go.mod
│  go.sum
│  main.go                    # 入口文件,引用 boot.go 和 router.go 进行初始化,然后Run
│  README.MD
├─app
│  ├─api
│  │      hello.go
│  ├─dao
│  │      .gitkeep
│  ├─model
│  │      .gitkeep
│  └─service
│          .gitkeep
├─boot
│      .gitkeep
│      boot.go                 # 启动第一个执行的 init, 主要是加载packed内容或者其他自定义的行为
├─config
│      .gitkeep
│      config.toml
├─docker
│      .gitkeep
├─document
│      .gitkeep
├─i18n
│      .gitkeep
├─packed
│      packed.go               # packed功能实现文件,用来打包静态资源
├─public
│  ├─html
│  │      .gitkeep
│  ├─plugin
│  │      .gitkeep
│  └─resource
│      ├─css
│      │      .gitkeep
│      ├─image
│      │      .gitkeep
│      └─js
│              .gitkeep
├─router
│      .gitkeep
│      router.go               # 根据 config 配置内容生成server实例,然后Group 路由表,后续路由设定也在这里
└─template.gitkeep

这个框架很有特点,把server内容分为api/dao/model/service几个部分,编写代码时,只需要按照框架设定编写就可以,并且会有很多好处。

api: 用来与router.go 配合,完成路由与接口处理工作。一般使用model下的struct进行API参数验证,然后调用service进行数据处理,然后返回json结果
dao: 通过gf gen dao命令可以通过数据库反向生成各个表的ORM接口(internal文件夹内部文件不能改),方便在此基础上完善与数据库交互功能实现
model: 一般gf gen dao命令在这里生成一个model.go文件,存储了struct和ORM/json映射关系。用户可以在此处创新自己其他用途的model文件
service: 小功能的集合处,真正实现的地方。这里包含所有的中间件、特殊服务、数据处理等。

docker文件夹,document文件夹,i18n文件夹看名字就知道用途,等用的时候再参考。
public用来存放使用template或者编译好的web站点静态文件。

配置项目数据

一个完整的配置数据如下:

[server]# 基本配置address             = ":80"                        # 本地监听地址。默认":80"httpsAddr           = ":443"                       # TLS/HTTPS配置,同时需要配置证书和密钥。默认关闭httpsCertPath       = ""                           # TLS/HTTPS证书文件本地路径,建议使用绝对路径。默认关闭httpsKeyPath        = ""                           # TLS/HTTPS密钥文件本地路径,建议使用绝对路径。默认关闭readTimeout         = "60s"                        # 请求读取超时时间,一般不需要配置。默认为60秒writeTimeout        = "0"                          # 数据返回写入超时时间,一般不需要配置。默认不超时(0)idleTimeout         = "60s"                        # 仅当Keep-Alive开启时有效,请求闲置时间。默认为60秒maxHeaderBytes      = "10240"                      # 请求Header大小限制(Byte)。默认为10KBkeepAlive           = true                         # 是否开启Keep-Alive功能。默认trueserverAgent         = "GoFrame HTTP Server"        # 服务端Agent信息。默认为"GoFrame HTTP Server"# 静态服务配置indexFiles          = ["index.html","index.htm"]   # 自动首页静态文件检索。默认为["index.html", "index.htm"]indexFolder         = false                        # 当访问静态文件目录时,是否展示目录下的文件列表。默认关闭,那么请求将返回403serverRoot          = "./statics"                  # 静态文件服务的目录根路径,配置时自动开启静态文件服务。默认关闭searchPaths         = ["./public/www","./www"]     # 提供静态文件服务时额外的文件搜索路径,当根路径找不到时则按照顺序在搜索目录查找。默认关闭fileServerEnabled   = false                        # 静态文件服务总开关。默认false# Cookie配置cookieMaxAge        = "30d"              # Cookie有效期。默认为365天cookiePath          = "/"                # Cookie有效路径。默认为"/"表示全站所有路径下有效cookieDomain        = ""                 # Cookie有效域名。默认为当前配置Cookie时的域名# Sessions配置sessionMaxAge       = "24h"              # Session有效期。默认为24小时sessionIdName       = "quabg-sess"       # SessionId的键名名称。默认为gfsessionidsessionCookieOutput = true               # Session特性开启时,是否将SessionId返回到Cookie中。默认truesessionPath         = "./data/sessions"  # Session存储的文件目录路径。默认为当前系统临时目录下的gsessions目录# Logging配置,指的是服务器本身的logPath             = "./data/logs/"     # 日志文件存储目录路径,建议使用绝对路径。默认为空,表示关闭logStdout           = true               # 日志是否输出到终端。默认为trueerrorStack          = true               # 当Server捕获到异常时是否记录堆栈信息到日志中。默认为trueerrorLogEnabled     = true               # 是否记录异常日志信息到日志中。默认为trueerrorLogPattern     = "ser_err-{Ymd}.log"# 异常错误日志文件格式。默认为"error-{Ymd}.log"accessLogEnabled    = false              # 是否记录访问日志。默认为falseaccessLogPattern    = "ser_acc-{Ymd}.log"# 访问日志文件格式。默认为"access-{Ymd}.log"# PProf配置pprofEnabled        = false              # 是否开启PProf性能调试特性。默认为falsepprofPattern        = ""                 # 开启PProf时有效,表示PProf特性的页面访问路径,对当前Server绑定的所有域名有效。# 其他配置clientMaxBodySize   = 8102410240         # 客户端最大Body上传限制大小,影响文件上传大小(Byte)。默认为8*1024*1024=8MB *10=80MformParsingMemory   = 1048576            # 解析表单时的缓冲区大小(Byte),一般不需要配置。默认为1024*1024=1MBnameToUriType       = 0                  # 路由注册中使用对象注册时的路由生成规则。默认为0routeOverWrite      = false              # 当遇到重复路由注册时是否强制覆盖。默认为false,重复路由存在时将会在启动时报错退出dumpRouterMap       = true               # 是否在Server启动时打印所有的路由列表。默认为truegraceful            = false              # 是否开启平滑重启特性,开启时将会在本地增加10000的本地TCP端口用于进程间通信。默认false             gracefulTimeout     = 2                  # 父进程在平滑重启后多少秒退出,默认2秒。若请求耗时大于该值,可能会导致请求中断# gf gen and build
[gfcli][[gfcli.gen.dao]]link   = "mysql:admin:admin123@tcp(127.0.0.1:3306)/db_test"removePrefix = "t_"jsonCase = "CamelLower"gJsonSupport = true[gfcli.build]name     = "quabg"arch     = "amd64"system   = "linux,windows"mod      = "none"cgo      = 0pack     = ""version  = ""output   = "./bin"extra    = ""# 业务logger组件配置
[logger]path                 = "./data/logs/"  # 日志文件路径。默认为空,表示关闭,仅输出到终端file                 = "c_{Y-m-d}.log" # 日志文件格式。默认为"{Y-m-d}.log"prefix               = ""              # 日志内容输出前缀。默认为空level                = "all"           # 日志输出级别ctxKeys              = []              # Context上下文变量名称,自动打印Context的变量到日志中。默认为空headerPrint          = true            # 是否打印日志的头信息。默认truestdoutPrint          = true            # 日志是否同时输出到终端。默认truerotateSize           = 0               # 按照日志文件大小对文件进行滚动切分。默认为0,表示关闭滚动切分特性rotateExpire         = 0               # 按照日志文件时间间隔对文件滚动切分。默认为0,表示关闭滚动切分特性rotateBackupLimit    = 0               # 按照切分的文件数量清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除rotateBackupExpire   = 0               # 按照切分的文件有效期清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除rotateBackupCompress = 0               # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩rotateCheckInterval  = "1h"            # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时writerColorEnable    = false           # 日志文件是否带上颜色。默认false,表示不带颜色# Database.
[database]link      = "mysql:admin:admin123@tcp(127.0.0.1:3306)/db_test"debug     = truecharset   = "utf8"ctxStrict = false# Database logger.[database.logger]Path   = ""Level  = "error"Stdout = true# User config for tcp server
[tcpserver]Address = ":20012"MqAddress = "tcp://127.0.0.1:1883"             # MQTT 服务器地址MqPrefix = "/test/dev/"                        # 设备发送 tx, 设备接收 rxMqUser = "test"MqPwd = "Mqtt_test"

其中

  • gf tool部分完整参数参考说明参见link。
  • ORM Database 完整参考

数据表构建

受限查看数据服务器编码信息:

show variables like '%character%';
show variables like 'collation%';

为了对unicode编码支持良好,更多使用utf8mb4/utf8mb4_0900_ai_ci编码.

创建表

CREATE DATABASE IF NOT EXISTS `db_test` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_bin';
USE `db_test`;
--------------------------------用户相关信息--------------------------------------------- user 角色表
create table IF NOT EXISTS t_user
(`id`      integer primary key auto_increment comment '用户ID',`name`    nvarchar(255)                 not null comment '用户名称',`pwd`     nvarchar(255)                 not null comment '加盐后的密码MD5',`phone`   nvarchar(255)                 not null comment '用户手机号码',`email`   nvarchar(255)  default ''         null comment '用户邮箱',`salt`    nvarchar(10)                  not null comment '密码salt',`state`   int          default 1            null comment '用户状态,0冻结,1激活',`role`    int          default 1            null comment '角色Index',`group`   int          default 1            null comment '组Index',`crt_tm`  int          default 0            null comment '创建时间戳',`extras`  json                              null comment '附加信息'-- `wechat`  nvarchar(2555)  default ''    not null comment '微信关联ID',
) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8mb4 comment '用户信息表';
INSERT INTO t_user (`id`, `name`, `pwd`, `phone`, `email`, `salt`, `state`, `role`, `group`, `crt_tm`, `extras`) VALUES (10000, '张三', '110110', '01234567890', '', '123123', 1, 10, 100, 1640923199, null);-- role 角色表
-- 部分场景可以直接省略为 0,1,2,3 来区分权限
create table IF NOT EXISTS t_role
(`id`      integer primary key auto_increment comment '角色ID',`name`    nvarchar(255)                 not null comment '角色名称',`description` nvarchar(255)                      comment '角色描述', `extras`  json                              null comment '附加信息'
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 comment '角色信息表';
INSERT INTO t_role (`id`, `name`, `description`, `extras`) VALUES (10, '站长', '组拥有者,可使用所有功能', null);
INSERT INTO t_role (`id`, `name`, `description`, `extras`) VALUES (11, '管理员', '管理员,可以增删改查大部分功能', null);
INSERT INTO t_role (`id`, `name`, `description`, `extras`) VALUES (12, '普通用户', '普通用户,可以访问控制,不能修改信息', null);-- group 用户组/组织信息
create table IF NOT EXISTS t_group
(`id`      integer primary key auto_increment comment '组ID',`name`    nvarchar(255)                 not null comment '组名称',`description` nvarchar(255)                      comment '组描述',`extras`  json                              null comment '附加信息'
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 comment '组信息表';
INSERT INTO t_group (`id`, `name`, `description`, `extras`) VALUES (100, '默认组', '默认的组织,修改后使用', null);
-------------------------------网站相关信息---------------------------------------------- 导航相关信息配置
create table IF NOT EXISTS t_nav
(`id`      integer primary key auto_increment comment 'navi menu ID',`name`    nvarchar(255)                 not null comment '导航名称',`keyword` nvarchar(255)                 not null comment 'English simple key word',`role`    int           default 0           null comment '菜单所有权,0不限制,其他进行匹配',`level`   int           default 0           null comment '菜单层次等级',`pid`     int           default 0           null comment '父级菜单ID',`icon`    nvarchar(255)                 not null comment '图标',`extras`  json                              null comment '附加信息'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 comment '菜单信息表';-- 网站对应组信息表
create table IF NOT EXISTS t_group_cfg
(`id`      integer primary key auto_increment comment 'config ID',`name`    nvarchar(255)                 not null comment '配置名称',`title`   nvarchar(255)                 not null comment '网站名称',`gid`     int           default 0           null comment '配置拥有组ID',`logo`    nvarchar(255)                 not null comment '图标',`extras`  json                              null comment '附加信息'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 comment '网站配置信息表';--------------------------------业务相关信息-------------------------------------------
-- devices 设备表
create table IF NOT EXISTS t_dev
(`id`      integer primary key auto_increment comment '设备ID',`name`    nvarchar(255)                 not null comment '设备名称',`uid`     nvarchar(255)                 not null comment '设备UID',`pwd`     nvarchar(255)                 not null comment '通信密码',`state`   int          default 0            null comment '通信状态,0未激活,1激活',`gid`     int                           not null comment 'Goup ID',`type`    int                           not null comment '设备类型',`crt_tm`  int          default 0            null comment '创建时间戳',`extras`  json                              null comment '附加信息'
) ENGINE=InnoDB AUTO_INCREMENT=200001 DEFAULT CHARSET=utf8mb4 comment '设备信息表';-- submods 设备模块表
create table IF NOT EXISTS t_submod
(`id`      integer primary key auto_increment comment '模块ID',`name`    nvarchar(255)                 not null comment '模块名称',`idx`     int                           not null comment '模块的子Idx',`type`    int                           not null comment '模块类型',`crt_tm`  int          default 0            null comment '创建时间戳',`extras`  json                              null comment '附加信息'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 comment '设备子模块信息表';

然后在项目根目录下,使用gf工具生成dao文件gf gen dao, gf run main.go:

|   Dockerfile
|   go.mod
|   go.sum
|   main.go
|   README.MD
|
+---app
|   +---api
|   |       hello.go
|   |
|   +---dao
|   |   |   .gitkeep
|   |   |   dev.go
|   |   |   group.go
|   |   |   group_cfg.go
|   |   |   nav.go
|   |   |   role.go
|   |   |   submod.go
|   |   |   user.go
|   |   |
|   |   \---internal
|   |           dev.go
|   |           group.go
|   |           group_cfg.go
|   |           nav.go
|   |           role.go
|   |           submod.go
|   |           user.go
|   |
|   +---model
|   |       .gitkeep
|   |       model.go
|   |
|   \---service
|           .gitkeep
|
+---boot
|       .gitkeep
|       boot.go
|
+---config
|       .gitkeep
|       config.toml
|
+---data
|   +---logs
|   |       2021-12-30.log
|   |
|   \---sessions
|       \---default
+---docker
|       .gitkeep
|
+---document
|       .gitkeep
|
+---i18n
|       .gitkeep
|
+---logs
|       2021-12-27.log
|
+---packed
|       packed.go
|
+---public
|   +---html
|   |       .gitkeep
|   |
|   +---plugin
|   |       .gitkeep
|   |
|   \---resource
|       +---css
|       |       .gitkeep
|       |
|       +---image
|       |       .gitkeep
|       |
|       \---js
|               .gitkeep
|
+---router
|       .gitkeep
|       router.go
|
\---template.gitkeep

可以看到,工具在dao文件夹下为dev,group,group_cfg,nav,role,submod,user等表生成了对应的db封装,在model文件夹下生成了结构体和ORM/JSON映射。

当run起来后,产生了我们配置的logs和session文件夹,里面存放运行时log内容和session文件。

到此,一个web后端所需要的所有数据都已经准备完毕,下面就是API和中间件的设计了。

路由

一个正常的web站点,逃不开用户/鉴权/权限/api这四个基础话题,这里都简单说明。

1. API

  • API使用分层设计方式
  • 基于JSON的POST模式进行设计
  • 内部使用各种中间件进行鉴权认证一类的功能
  • 如果有文档需求,在代码完成后,参考GF官方推荐方式进行集成。
  • API接口支持跨域

API的返回信息参考如下格式

{"code": 1000,  // 1000 正常,1001 常规错误,1002 鉴权错误,1003 内部错误..."msg": "ok", // 提示信息,在非1000时,可以根据这个提示用户行为"data": object/array,  // 动态数据,根据请求接口而定"notify":[]  // 服务器给客户端推送的消息,一般可以不适用
}

其中,json字段命名规范参考于gf gen dao -h,使用CamelLower风格,即除了首歌单词外,首字母大写,其他小写。

Case Example
Camel AnyKindOfString
CamelLower anyKindOfString
Snake any_kind_of_string
SnakeScreaming ANY_KIND_OF_STRING
SnakeFirstUpper rgb_code_md5
Kebab any-kind-of-string
KebabScreaming ANY-KIND-OF-STRING

2. 鉴权

目前有很多鉴权方式,JWT和Token是两种比较常见的,网络上有各种讨论他们的优缺点,这里不作为考虑的重点,我们选用一种折衷方式的简单实现:

  • 鉴权认证信息放在HTTP Header部分,使用 Authorization 字段传递鉴权信息,web或者客户端自行选择存储方式。
  • 鉴权认证信息格式使用JWT token方式,内部存储部分不敏感信息,需要的时候在服务端进行再次认证/查询。
  • 鉴权信息包含salt信息,在服务器除了验证密钥,还与缓存的用户salt数据核对,发现不匹配时,说明此用户token失效。

对于所有的请求,如果服务器没有异常,正常返回200,客户端根据json中的code进行错误处理。

对于websocket服务,需要考虑token传递问题和心跳问题,所以优化:

  • token可以根据实际情况在url或者header传递
  • nginx等服务需要添加超时比实际业务更长一点 参考link
http {server {location /ws {root   html;index  index.html index.htm;proxy_pass xxx.xxx.xxx.xx:9527;proxy_http_version 1.1;proxy_connect_timeout 4s;proxy_read_timeout 700s;      # 需要比业务心跳更长一点proxy_send_timeout 12s;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "Upgrade";}}
}
  • websocket接口层与API的接口层,尽量一致,方便后续做SDK
{"request": "/api/user/login","header": {...},"body": {...}
}

3. 用户

用户基础信息通过专用接口返回,通过role区分返回信息多少。通过group区分组织,然后通过group_cfg来获取单独网站配置信息,通过role的nav的绑定关系,确定菜单导航显示的功能。

用户的登陆暂不考虑第三方问题。

后期缺少auth2和支付相关功能

4. 权限

权限目前只通过role base确定,不支持定制。
权限大多对于定制化业务,需要设计的管理方式也不一致,不通用。

Code

鉴权部分需要通过中间件,存放在context中,统一处理。

如下时一个常用的几个中间件:

 file:  model/context.goconst (ContextKey = "ReqCtx" // 上下文变量存储键名
)// Context 请求上下文结构
type Context struct {Session    *ghttp.Session  // 当前Session管理对象ReqUserCtx *ContextUserReq // 上下文用户信息
}// ContextUserReq 用户请求过来时,提取 token信息,填充此结构。后续使用时,可以追加新的类型
type ContextUserReq struct {Token string `json:"-"`     // Header or parameter tokenId    int    `json:"id"`    // 用户IDSalt  string `json:"salt"`  // Salt,需要与服务器salt比对Role  int    `json:"role"`  // 用户角色Group int    `json:"group"` // 用户组
}file: service/context.go// Context 上下文管理服务, 类型在model.Context 中定义
var Context = new(contextService)type contextService struct{}// Init 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改。请求开始时调用初始化
func (s *contextService) Init(r *ghttp.Request, customCtx *model.Context) {r.SetCtxVar(model.ContextKey, customCtx)
}// Get 获得上下文变量,如果没有设置,那么返回nil
func (s *contextService) Get(ctx context.Context) *model.Context {value := ctx.Value(model.ContextKey)if value == nil {return nil}if localCtx, ok := value.(*model.Context); ok {return localCtx}return nil
}// SetUser 将上下文信息设置到上下文请求中,注意是完整覆盖
func (s *contextService) SetUser(ctx context.Context, ctxUser *model.ContextUserReq) {s.Get(ctx).ReqUserCtx = ctxUser
}file: middleware.go
// Middleware 中间件管理服务
var Middleware = new(middlewareService)type middlewareService struct{}// Ctx 上下文对象提取. 这里初始化,jwt时进行认证
func (s *middlewareService) Ctx(r *ghttp.Request) {reqCtx := &model.Context{Session:    r.Session,ReqUserCtx: nil,}Context.Init(r, reqCtx) //在进行jwt认证时,会重新设置ReqUserCtxr.Middleware.Next()
}// JwtAuth JWT 鉴权中间件,JWT+Token方式认证
func (s *middlewareService) JwtAuth(r *ghttp.Request) {if _, err := AuthHandler(r); err == nil {// 根据实际情况,处理Salt的核对// 这里暂不做处理r.Middleware.Next()} else {_ = r.Response.WriteJson(MakeJsonErr(err.Error()))}
}// CORS 允许接口跨域请求
func (s *middlewareService) CORS(r *ghttp.Request) {r.Response.CORSDefault()r.Middleware.Next()
}

一个参考认证的结构:
https://goframe.org/pages/viewpage.action?pageId=7302150
https://goframe.org/pages/viewpage.action?pageId=1114367

Context定义

// Context 上下文管理服务, 类型在model.Context 中定义
var Context = new(contextService)type contextService struct{}// Init 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改。请求开始时调用初始化
func (s *contextService) Init(r *ghttp.Request, customCtx *model.Context) {r.SetCtxVar(model.ContextKey, customCtx)
}// Get 获得上下文变量,如果没有设置,那么返回nil
func (s *contextService) Get(ctx context.Context) *model.Context {value := ctx.Value(model.ContextKey)if value == nil {return nil}if localCtx, ok := value.(*model.Context); ok {return localCtx}return nil
}// SetUser 将上下文信息设置到上下文请求中,注意是完整覆盖
func (s *contextService) SetUser(ctx context.Context, ctxUser *model.ContextUserReq) {s.Get(ctx).ReqUserCtx = ctxUser
}

JWT 不使用 gf-jwt,因为那个fork gin-jwt 并且很久不更新,原始代码有问题,所以参考如下,自己做一个:

import ("errors""github.com/gogf/gf/frame/g""github.com/gogf/gf/net/ghttp"jwt "github.com/golang-jwt/jwt""quabg/app/model""strings""time"
)// https://blog.csdn.net/shachao888/article/details/111072282
// type StandardClaims struct {//  Audience  string `json:"aud,omitempty"` //该JWT所面向的用户
//  ExpiresAt int64  `json:"exp,omitempty"` //token什么时候过期
//  Id        string `json:"jti,omitempty"` //ID为web token提供唯一标识
//  IssuedAt  int64  `json:"iat,omitempty"` //在什么时候签发的token
//  Issuer    string `json:"iss,omitempty"` //该JWT的签发者
//  NotBefore int64  `json:"nbf,omitempty"` //token在此时间之前不能被接收处理
//  Subject   string `json:"sub,omitempty"` //
// }var (_jwtSecret []byte
)func init() {_jwtSecret = g.Cfg().GetBytes("jwtKey")
}type Claims struct {model.ContextUserReqjwt.StandardClaims
}// AuthHandler 接收http请求,解析token相关内容,然后给出token body
func AuthHandler(r *ghttp.Request) (*Claims, error) {// token from header Authorization or query parameter tokentoken := r.Request.Header.Get("Authorization")if len(token) < 8 {token = r.GetQueryString("token")if len(token) < 8 {return nil, errors.New("缺少token相关参数")}} else {// split header token  => Bearer/Basic TEphZXI6MTIzNDU2token = strings.TrimSpace(token)tks := strings.Split(token, " ")if len(tks) != 2 {return nil, errors.New("token格式错误")}token = tks[1]}// parsecla, err := ParseToken(token)if err == nil {// check timeif cla.ExpiresAt < time.Now().Unix() {return nil, errors.New("token已过期")}}// update contentContext.SetUser(r.Context(), &cla.ContextUserReq)return cla, err
}// GenerateToken 产生token的函数
func GenerateToken(id, role, group int, salt string) (string, error) {nowTime := time.Now()expireTime := nowTime.Add(24 * time.Hour)claims := Claims{model.ContextUserReq{Id:    id,Salt:  salt,Role:  role,Group: group,},jwt.StandardClaims{ExpiresAt: expireTime.Unix(),Issuer:    "gf-server",},}// maketokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)token, err := tokenClaims.SignedString(_jwtSecret)return token, err
}// ParseToken 验证token的函数
func ParseToken(token string) (*Claims, error) {tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {return _jwtSecret, nil})if tokenClaims != nil {if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {return claims, nil}}// errorreturn nil, err
}

最终路由规则:

func init() {s := g.Server()s.Group("/api", func(group *ghttp.RouterGroup) {group.Middleware(service.Middleware.CORS, service.Middleware.Ctx)// API 文档和调试接口// ...// 不需要Auth, 但需要验证码group.POST("/user/reg", nil)   // 根据实际情况开放group.POST("/user/login", nil) // 登录group.POST("/user/find", nil)  // 查找账户信息,短信或邮件发送// Usergroup.Group("/user", func(group *ghttp.RouterGroup) {group.Middleware(service.Middleware.JwtAuth)group.POST("/reset_pwd", nil)group.POST("/get", nil)     // 获取用户信息group.POST("/get_all", nil) // 获取用户信息,包含分组,导航,分组配置等group.POST("/edit", nil)    // 修改部分用户信息// 子用户管理 【管理员和站长权限】group.Group("/sub", func(group *ghttp.RouterGroup) {group.POST("/list", nil) // 获取所有用户信息group.POST("/add", nil)  // 获取添加子用户group.POST("/edit", nil) // 修改子用户信息,ID=-1意味新增group.POST("/del", nil)  // 删除子用户})})// Role,站长/管理员/普通用户group.Group("/role", func(group *ghttp.RouterGroup) {group.Middleware(service.Middleware.JwtAuth)group.POST("/list", nil) // 获取信息列表group.POST("/get", nil)  // 获取ID对应信息//group.POST("/edit", nil)  // 修改配置信息,ID=-1意味着新增//group.POST("/del", nil) // 删除ID对应信息// cfg -> navgroup.Group("/nav", func(group *ghttp.RouterGroup) {group.POST("/list", nil) // 获取信息列表group.POST("/get", nil)  // 获取ID对应信息group.POST("/edit", nil) // 修改配置信息,ID=-1意味着新增 【管理员】//group.POST("/del", nil) // 删除ID对应信息})})// Group,分组/组织group.Group("/group", func(group *ghttp.RouterGroup) {group.Middleware(service.Middleware.JwtAuth)group.POST("/list", nil) // 获取信息列表,只有【站长】有此权限group.POST("/get", nil)  // 获取ID对应信息group.POST("/edit", nil) // 修改配置信息,ID=-1意味着新增,只有【管理员】有此权限group.POST("/del", nil)  // 删除ID对应信息,只有【站长】有此权限group.POST("/cfg", nil)  // 修改group的网站配置信息,只有【管理员】有此权限})// 短信,微信等后台服务group.Group("/util", func(group *ghttp.RouterGroup) {group.Middleware(service.Middleware.JwtAuth)group.POST("/msg_sms", nil)    // 推送信息到短信group.POST("/msg_qq", nil)     // 推送信息到QQgroup.POST("/msg_wechat", nil) // 推送信息到微信})// 业务相关部分,根据实际情况使用// dev// submod})
}

验证码

使用自己生成的go get -u github.com/mojocn/base64Captcha

// Captcha 中间件管理服务
var Captcha = new(captchaService)func init() {//init rand seedrand.Seed(time.Now().UnixNano())// captchaCaptcha.Store = base64Captcha.NewMemoryStore(1024, 10*time.Minute)Captcha.Driver = base64Captcha.NewDriverString(150,300,0,0,4,"%#=qwe23456789rtyupasdfghjkzxcvbnm",&color.RGBA{0, 0, 0, 0},nil,[]string{},)
}// captchaService captcha basic information.
type captchaService struct {Driver *base64Captcha.DriverStringStore  base64Captcha.Store
}// GenerateIdAndImage 生成一组验证码
func (c *captchaService) GenerateIdAndImage() (string, string, string, error) {id, content, answer := c.Driver.GenerateIdQuestionAnswer()item, err := c.Driver.DrawCaptcha(content)if err != nil {return "", "", "", err}err = c.Store.Set(id, answer)if err != nil {return "", "", "", err}b64s := item.EncodeB64string()return id, b64s, answer, nil
}// VerifyIdAndAns 验证Id和answer
func (c *captchaService) VerifyIdAndAns(id, ans string) bool {return c.Store.Verify(id, ans, true)
}

本片后记

因为GF框架最近更新到2.0,对于API文档和请求处理接口更友好,整体组织形式也发生了变化,所以,后续依据2.0框架完成。

GoFrame Step by Step Demo - P1相关推荐

  1. SECRET SHARING STEP BY STEP

    In this blog article I will show the different types of secret sharing methods especially the common ...

  2. Followme Devops step by step

    接着上次分享的devops历程[Followme Devops实践之路], 大家希望能够出一个step by step手册, 那今天我就来和手把手来一起搭建这么一套环境, 演示整个过程! 实验环境需要 ...

  3. RDL(C) Report Design Step by Step 3: Mail Label

    RDL(C) Report Design Step by Step 3: Mail Label Crystal Report在报表向导中提供了三种向导类型给用户进行选择--Standard.Cross ...

  4. RDL(C) Report Design Step by Step 2: SubReport

    RDL(C) Report Design Step by Step 2: SubReport 前两天,有网友在Blog上评论要求将子报表的配置贴出来,由于这两天有些别的事情,所以耽搁了:另外,自己也比 ...

  5. RDL(C) Report Design Step by Step 1: DrillThrough Report

    RDL(C) Report Design Step by Step 1: DrillThrough Report 前一段时间,发了几篇关于RDLC报表的随笔,由于这些随笔中没有关于报表设计方面的内容, ...

  6. Enterprise Library Step By Step系列(十六):使用AppSetting Application Block

    Enterprise Library Step By Step系列(十六):使用AppSetting Application Block Terrylee,2005年12月07日 概述 AppSett ...

  7. 吴恩达Coursera深度学习课程 DeepLearning.ai 编程作业——Convolution model:step by step and application (4.1)

    一.Convolutional Neural Networks: Step by Step Welcome to Course 4's first assignment! In this assign ...

  8. RDL(C) Report Design Step by Step

    RDL(C) Report Design Step by Step 1: DrillThrough Report 前一段时间,发了几篇关于RDLC报表的随笔,由于这些随笔中没有关于报表设计方面的内容, ...

  9. 【Step By Step】将Dotnet Core部署到Docker下

    一.使用.Net Core构建WebAPI并访问Docker中的Mysql数据库 这个的过程大概与我之前的文章<尝试.Net Core-使用.Net Core + Entity FrameWor ...

最新文章

  1. 现在学java还是python好_该学Java还是Python?
  2. MF训练套件(1):如何去除应用标题?
  3. PyQt5 图形界面-基础准备:安装Qt Creater、Qt Designer工具,使用Qt Designer设计界面,安装PyQt5工具库
  4. 成功开源项目证明Web是开源最大成功
  5. 程序员如何做好技术规划?
  6. ActiveMQ网络连接
  7. .NET 6新特性试用 | Controller支持IAsyncDisposable
  8. 又一家公司被吃到上市了,每天卖出490吨辣条
  9. 【报告分享】激荡2020--吴晓波疫情特别演讲PPT.pdf(附下载链接)
  10. FluentAPI --- 用C#写的JS代码生成器
  11. php中result的值,PHP 返回所取得行中字段的值
  12. MySQL利用关系代数进行查询_mysql – 将SQL SELECT查询转换为相应的/等效的关系代数...
  13. 关于无穷级数收敛的充要条件的猜想
  14. 华为人均工资高达70万,但先看看华为员工的16项标准
  15. 生成PDF的几种方式
  16. 绿云签约服务的酒店数量超越2万家,同比增长超过15%
  17. B样条曲线与曲面相关知识点汇总
  18. 体验5款陌生社交App后,发现全是“金钱”套路
  19. PyCharm代码格式化插件
  20. 2014522420145238《信息安全系统设计基础》实验五

热门文章

  1. 《奇葩说》詹青云:熬过那些日子,你才有资格过不被人左右的人生
  2. Vcastr 2.2 flv 网络播放器
  3. android5.1官方下载,vllo官方下载
  4. 《视频直播技术详解》之(三):编码和封装
  5. 【2021-03-17】JS逆向之某实时票房榜数据解密
  6. 【狼人杀】初阶教学——基本规则
  7. 生产者与消费者模式的概念、运用、3种实现
  8. C++ STL prev()函数
  9. 三星Q90 全景声回音壁 参数发布
  10. IE 10 SCRIPT5022: InvalidCharacterError错误解决办法