前言知识
-
gin.Default() 创建一个带有默认中间件的路由引擎
-
GET方法
- GET(路由地址,函数)
- 函数的参数表示一个HTTP 请求的上下文,包括请求和响应
ps: POST方法类似
-
Run方法
- 执行服务程序,参数为【地址:端口】,如:“127.0.0.1:9090”
-
http.StatusOK 表示 状态码200,其他类似
get 请求
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/get", getMsg)
r.Run(":9090")
}
func getMsg(context *gin.Context) {
// 获取传入的 name
name := context.Query("name")
// 返回 字符串 格式
context.String(http.StatusOK, "欢迎您: %s", name)
// 返回 json 格式
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "返回信息",
"data": "欢迎您:" + name,
})
}
post 请求
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/post", postMsg)
r.Run(":9090")
}
func postMsg(context *gin.Context) {
name := context.DefaultPostForm("name", "Gin")
fmt.Println(name)
form, b := context.GetPostForm("name")
fmt.Println(form, b)
context.JSON(http.StatusOK, "欢迎您:"+name)
}
重定向
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 外部链接
r.GET("/redirect1", func(context *gin.Context) {
url := "http://www.baidu.com"
context.Redirect(http.StatusMovedPermanently, url)
})
// 自定义重定向
r.GET("/redirect2", func(context *gin.Context) {
context.Request.URL.Path = "/testRedirect"
r.HandleContext(context)
})
// 自定义链接
r.GET("/testRedirect", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "响应成功",
})
})
r.Run(":9090")
}
获取第三方数据
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/getOtherData", func(context *gin.Context) {
url := "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png"
// 使用http.Get方法向指定的URL发送一个GET请求,并接收响应
res, err := http.Get(url)
// 检查是否在请求过程中发生错误,或者响应的状态码是否不是200(OK)
if err != nil || res.StatusCode != http.StatusOK {
context.Status(http.StatusServiceUnavailable)
return
}
// 请求体内容
body := res.Body
// 请求体长度
contentLen := res.ContentLength
// 请求媒体类型
contentType := res.Header.Get("Content-Type")
// 将获取到的数据以及相关信息发送给客户端
context.DataFromReader(http.StatusOK, contentLen, contentType, body, nil)
})
r.Run(":9090")
}
多形式渲染
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/json", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"html": "<b>hello</b>",
})
})
r.GET("/html", func(context *gin.Context) {
context.PureJSON(http.StatusOK, gin.H{
"html": "<b>hello</b>",
})
})
r.GET("/xml", func(context *gin.Context) {
type Message struct {
Name string
Msg string
Age int
}
info := Message{}
info.Name = "FAN"
info.Msg = "hello"
info.Age = 23
context.XML(http.StatusOK, info)
})
r.GET("yaml", func(context *gin.Context) {
context.YAML(http.StatusOK, gin.H{
"message": "hello",
"status": 200,
})
})
r.Run(":9090")
}
文件服务器
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/file", fileServer)
r.Run(":9090")
}
func fileServer(context *gin.Context) {
// 存储路径
path := "D:/2-golang/img/"
// 文件名
fileName := path + context.Query("name")
context.File(fileName)
}
单文件上传
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/upload", func(context *gin.Context) {
file, err := context.FormFile("fileName")
if err != nil {
context.String(http.StatusBadRequest, "文件上传错误!")
}
dst := "D:/2-golang/img/"
context.SaveUploadedFile(file, dst+file.Filename)
context.String(http.StatusOK, fmt.Sprintf("%s 上传成功", file.Filename))
})
r.Run(":9090")
}
多文件上传
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/allUpload", fileServe)
r.Run(":9090")
}
func fileServe(context *gin.Context) {
form, err := context.MultipartForm()
if err != nil {
context.String(http.StatusBadRequest, "上传文件错误!")
}
// 多文件上传原理是一样的,只是多了个循环来保存文件
dst := "D:/2-golang/img/"
files := form.File["file_key"]
for _, file := range files {
context.SaveUploadedFile(file, dst+file.Filename)
}
context.String(http.StatusOK, fmt.Sprintf("%d 个文件上传成功!", len(files)))
}
自定义中间件
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
func main() {
//r := gin.New() //没有任何中间件的路由引擎
r := gin.Default() //默认路由引擎:Logger and Recovery middleware
r.Use(Middleware()) //先执行中间件,相当于过滤,减少不必要的服务器性能损耗
r.GET("/middleware", func(context *gin.Context) {
fmt.Println("服务端开始执行...")
name := context.Query("name")
ageStr := context.Query("age")
age, _ := strconv.Atoi(ageStr)
res := struct {
Name string `json:"name"` //添加标签
Age int `json:"age"`
}{name, age}
context.JSON(http.StatusOK, res)
})
r.Run(":9090")
}
func Middleware() gin.HandlerFunc {
return func(context *gin.Context) {
fmt.Println("中间件开始执行...")
name := context.Query("name")
ageStr := context.Query("age")
age, err := strconv.Atoi(ageStr)
//失败拦截
if err != nil {
context.AbortWithStatusJSON(http.StatusBadRequest, "输入格式错误,年龄不是整数")
}
if len(name) < 6 || len(name) > 12 {
context.AbortWithStatusJSON(http.StatusBadRequest, "用户名只能是6-12位")
}
if age < 0 || age > 100 {
context.AbortWithStatusJSON(http.StatusBadRequest, "年龄非法")
}
//成功进入服务端
context.Next()
}
}
登录中间件
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.Use(AuthMiddleware())
r.GET("/login", func(context *gin.Context) {
user := context.MustGet(gin.AuthUserKey).(string)
context.JSON(http.StatusOK, "登录成功"+"欢迎您:"+user)
})
r.Run(":9090")
}
func AuthMiddleware() gin.HandlerFunc {
// 静态添加
account := gin.Accounts{
"admin": "adminpw",
"system": "systempw",
}
// 动态添加
account["go"] = "go123"
account["gin"] = "gin123"
return gin.BasicAuth(account)
}
同步异步
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
"time"
)
func main() {
r := gin.Default()
r.GET("/sync", func(context *gin.Context) {
sync(context)
context.JSON(http.StatusOK, ">>>主程序同步已经执行<<<")
})
r.GET("/async", func(context *gin.Context) {
for i := 0; i < 6; i++ {
cCp := context.Copy()
go async(cCp, i) // 异步通过 go程 实现
}
context.JSON(http.StatusOK, ">>>主程序同步已经执行<<<")
})
r.Run(":9090")
}
func async(cp *gin.Context, i int) {
fmt.Println("第" + strconv.Itoa(i) + "个 go 程序开始执行:" + cp.Request.URL.Path)
time.Sleep(time.Second * 3)
println("第" + strconv.Itoa(i) + "个同步任务执行完成")
}
func sync(context *gin.Context) {
println("开始执行同步任务:" + context.Request.URL.Path)
time.Sleep(time.Second * 3)
println("同步任务执行完成")
}
多服务器程序运行
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
"net/http"
"time"
)
var g errgroup.Group // 使用 errgroup.Group 来并发启动这两个服务器
func main() {
// 服务器1
server01 := &http.Server{
Addr: ":9090",
Handler: router01(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
// 服务器2
server02 := &http.Server{
Addr: ":9092", // 别问为啥不用 91,问就是被占用了
Handler: router02(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
// 分别启动监听
g.Go(func() error {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
// 通过 g.Wait() 来等待它们的执行结果
if err := g.Wait(); err != nil {
fmt.Println("执行失败", err)
}
}
func router01() http.Handler {
r1 := gin.Default()
r1.GET("/MyServer01", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "服务器程序1启动成功",
})
})
return r1
}
func router02() http.Handler {
r1 := gin.Default()
r1.GET("/MyServer02", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "服务器程序2启动成功",
})
})
return r1
}
路由组
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type ResGroup struct {
Data string
Path string
}
func main() {
router := gin.Default()
// 路由分组1
v1 := router.Group("/v1") // /v1
{
// 路由分组1.1
r := v1.Group("/user") // /v1/user
r.GET("/login", login) // /v1/user/login
// // 路由分组1.1.1
r2 := r.Group("showInfo") // /v1/user/showInfo
r2.GET("/abstract", abstract) // /v1/user/showInfo/abstract
r2.GET("/detail", detail) // /v1/user/showInfo/detail
}
// 路由分组2
v2 := router.Group("/v2") // v2
{
v2.GET("/other", other) // /v2/other
}
router.Run(":9090")
}
func other(context *gin.Context) {
context.JSON(http.StatusOK, ResGroup{"detail", context.Request.URL.Path})
}
func detail(context *gin.Context) {
context.JSON(http.StatusOK, ResGroup{"detail", context.Request.URL.Path})
}
func abstract(context *gin.Context) {
context.JSON(http.StatusOK, ResGroup{"abstract", context.Request.URL.Path})
}
func login(context *gin.Context) {
context.JSON(http.StatusOK, ResGroup{"login", context.Request.URL.Path})
}
路由结构
- v1
- user
- login
- showInfo
- abstract
- detail
- user
- v2
- other
gin框架 bind
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Login struct {
UserName string `json:"user_name" binding:"required"` // 后端字段 UserName → 前端参数 user_name,必需
Password string `json:"password" binding:"required"`
Remark string `json:"remark" binding:"required"`
}
func main() {
r := gin.Default()
r.POST("/login", func(context *gin.Context) {
var login Login
// Bind的执行相当于:context从客户端接收数据,跟绑定的结构体进行比较
// 比较时,根据结构体编写的 json 名称、规则进行校验
err := context.Bind(&login) // 执行绑定,注意参数是地址(引用)
if err != nil {
context.JSON(http.StatusBadRequest, gin.H{
"msg": "绑定失败,参数错误",
"data": err.Error(),
})
return
}
if login.UserName == "user" && login.Password == "123456" {
context.JSON(http.StatusBadRequest, gin.H{
"msg": "登陆成功",
"data": "OK",
})
return
}
context.JSON(http.StatusBadRequest, gin.H{
"msg": "登陆失败",
"data": "error",
})
return
})
r.Run(":9090")
}
调用 restful 接口
ps:将上一模块的代码打包成exe文件,并执行起来,注意端口冲突问题(命令:go build 文件名)
- 逻辑顺序:
- 客户端(真实) → 服务端
- 服务端(模拟客户端) →第三方接口
- 第三方接口→服务端(模拟客户端)
- 服务端→客户端(真实)
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"time"
)
// 第三方接口字段
type UserAPI struct {
UserName string `json:"user_name"`
Password string `json:"password"`
Remark string `json:"remark"`
}
// 第三方接口返回字段封装
type TempData struct {
Msg string `json:"msg"`
Data string `json:"data"`
}
// 客户端请求字段
type ClientRequest struct {
UserName string `json:"user_name"`
Password string `json:"password"`
Remark string `json:"remark"`
Other interface{} `json:"other"`
}
// 服务端返回字段
type ClientResponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}
func main() {
//testAPI()
r := gin.Default()
r.POST("/getOtherAPI", getOtherAPI)
r.Run(":9092")
}
func getOtherAPI(context *gin.Context) {
var requestData ClientRequest
var response ClientResponse
err := context.Bind(&requestData) // 绑定请求数据
if err != nil {
response.Code = http.StatusBadRequest
response.Msg = "请求的参数错误"
response.Data = err
context.JSON(http.StatusBadRequest, response)
return
}
url := "http://127.0.0.1:9090/login"
user := UserAPI{requestData.UserName, requestData.Password, requestData.Remark}
data, err := getRestfulAPI(url, user, "application/json")
var temp TempData
json.Unmarshal(data, &temp) // 反序列化
response.Code = http.StatusOK
response.Msg = "请求数据成功"
response.Data = temp
context.JSON(http.StatusOK, response)
}
//func testAPI() {
// url := "http://127.0.0.1:9090/login"
// user := UserAPI{"user", "123456", "说明"}
// data, err := getRestfulAPI(url, user, "application/json")
// fmt.Println(data, err)
// var temp TempData
// json.Unmarshal(data, &temp)
// fmt.Println(temp)
//}
func getRestfulAPI(url string, data interface{}, contentType string) ([]byte, error) {
client := &http.Client{Timeout: 5 * time.Second} // 创建一个http客户端示例,去请求第三方
jsonStr, _ := json.Marshal(data) // 序列化数据
resp, err := client.Post(url, contentType, bytes.NewBuffer(jsonStr)) // 发起post请求
if err != nil {
fmt.Println("调用API接口出现了错误")
return nil, err
}
res, err := ioutil.ReadAll(resp.Body)
return res, err
}
参数校验(一)
uuid包:https://github.com/satori/go.uuid
因为作者更改了参数限制,导致会出问题 → 问题解决
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
uuid "github.com/gofrs/uuid"
"net/http"
"unicode/utf8"
)
type UserInfo struct {
Id string `validate:"uuid" json:"id"` // UUID 类型
Name string `validate:"checkName" json:"name"` // 自定义校验
Age uint8 `validate:"min=0,max=130" json:"age"` // 大于0小于130
}
var validate *validator.Validate
// 校验初始化
func init() {
validate = validator.New() // 初始化校验示例
validate.RegisterValidation("checkName", checkNameFunc) // 自定义校验方法
}
func checkNameFunc(fl validator.FieldLevel) bool {
count := utf8.RuneCountInString(fl.Field().String()) // 获取 Name 的字符串表示,并计算 Unicode 字符的数量
if count >= 2 && count <= 12 {
return true
}
return false
}
func main() {
uuid.Must(uuid.NewV4()) // 由 uuid包 生成 uuid
r := gin.Default()
var user UserInfo
r.POST("/validate", func(context *gin.Context) {
err := context.Bind(&user)
if err != nil {
context.JSON(http.StatusBadRequest, "请求参数错误")
return
}
err = validate.Struct(user)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
fmt.Println("错误的字段:", e.Field())
fmt.Println("错误的值:", e.Value())
fmt.Println("错误的tag:", e.Tag())
}
context.JSON(http.StatusBadRequest, "数据校验失败")
return
}
context.JSON(http.StatusOK, "数据校验成功")
})
r.Run(":9090")
}
参数校验(二)
validator包:https://github.com/go-playground/validator
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"net/http"
)
type ValUser struct {
Name string `validate:"required" json:"name"`
Age uint8 `validate:"gte=0,lte=130" json:"age"`
Email string `validate:"required,email" json:"email"`
// 切片数据类型
Address []ValAddress `validate:"dive" json:"address"`
}
type ValAddress struct {
Province string `validate:"required" json:"province"`
City string `validate:"required" json:"city"`
Phone string `validate:"numeric,len=11" json:"phone"`
}
var validate *validator.Validate
func init() {
validate = validator.New() // 初始化
}
func main() {
r := gin.Default()
var user ValUser
r.POST("/validate", func(context *gin.Context) {
//testData(context)
err := context.Bind(&user)
if err != nil {
context.JSON(http.StatusBadRequest, "参数错误,绑定失败")
return
}
// 参数校验
if validateUser(user) {
context.JSON(http.StatusOK, "数据校验成功")
return
}
context.JSON(http.StatusBadRequest, "校验失败")
return
})
r.Run(":9090")
}
//func testData(context *gin.Context) {
// address := ValAddress{
// Province: "浙江省",
// City: "杭州市",
// Phone: "13575121689",
// }
// user := ValUser{
// Name: "张三",
// Age: 15,
// Email: "1993036922@qq.com",
// Address: []ValAddress{address},
// }
// context.JSON(http.StatusOK, user)
//}
func validateUser(u ValUser) bool {
err := validate.Struct(u)
if err != nil {
for _, e := range err.(validator.ValidationErrors) {
fmt.Println("错误的字段:", e.Field())
fmt.Println("错误的值:", e.Value())
fmt.Println("错误的tag:", e.Tag())
}
return false
}
return true
}
swagger
swagger地址:https://github.com/swaggo/gin-swagger
引入步骤
- 下载swag及相关包
- go get -u github.com/swaggo/swag/cmd/swag
- go get -u github.com/swaggo/gin-swagger
- go get -u github.com/swaggo/files
- 实现 Api 代码
- 编写注释:参考https://github.com/swaggo/swag/blob/master/README.md#declarative-comments-format
- 在代码中使用 swagger 中间件
- 引入docs:如我的引入为 _ “2-golang/docs”,_ 表示不为包赋名
- 执行终端命令:swag init
如果出现 swag 不是命令,无法识别,则先执行: go install github.com/swaggo/swag/cmd/swag@latest
ps:文件名必须叫 main
package main
import (
_ "2-golang/docs"
"fmt"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"net/http"
)
type User struct {
UserName string `json:"user_name"`
Password string `json:"password"`
}
type Response struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data string `json:"data"`
}
func main() {
r := gin.Default()
// 使用 swagger 中间件
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
r.GET("/login", login)
r.POST("/register", register)
r.Run(":9090")
}
// @Tags 注册接口
// @Summary 注册
// @Description register
// @Accept json
// @Produce json
// @Param username formData string true "用户名"
// @Param password formData string true "密码"
// @Success 200 {string} json "{"code": 200, "data":"{"name":"username","password":"password"}","msg":"OK"}"
// @Router /register [post]
func register(context *gin.Context) {
var user User
// get 使用的是 query,post 使用的是 formData,而 Bind 只能绑定 query
// err := context.Bind(&user)
err := context.BindQuery(&user)
if err != nil {
context.JSON(http.StatusBadRequest, "数据错误")
return
}
res := Response{
Code: http.StatusOK,
Msg: "注册成功",
Data: "OK",
}
context.JSON(http.StatusOK, res)
}
// @Tags 登录接口
// @Summary 登录
// @Description login
// @Accept json
// @Produce json
// @Param username query string true "用户名"
// @Param password query string false "密码"
// @Success 200 {string} json "{"code": 200, "data":"{"name":"username","password":"password"}","msg":"OK"}"
// @Router /login [get]
func login(context *gin.Context) {
userName := context.Query("name")
pwd := context.Query("pwd")
fmt.Println(userName, pwd)
res := Response{}
res.Code = http.StatusOK
res.Msg = "登陆成功"
res.Data = "OK"
context.JSON(http.StatusOK, res)
}
gin框架cookie
package main
import (
"encoding/hex"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
var cookieName string
var cookieValue string
func main() {
r := gin.Default()
r.Use(cookieAuth())
r.GET("/cookie", func(context *gin.Context) {
name := context.Query("name")
if len(name) <= 0 {
context.JSON(http.StatusOK, "数据错误")
return
}
cookieName = "cookie_" + name
cookieValue = hex.EncodeToString([]byte(cookieName + "value"))
val, _ := context.Cookie(cookieName)
if val == "" {
context.String(http.StatusOK, "Cookie已经下发,下次登录有效", cookieName)
return
}
context.String(http.StatusOK, "验证成功,cookie值为:%s", val)
})
r.Run(":9090")
}
func cookieAuth() gin.HandlerFunc {
return func(context *gin.Context) {
val, _ := context.Cookie(cookieName)
if val == "" {
context.SetCookie(cookieName, cookieValue, 3600, "/", "localhost", true, true)
fmt.Println("cookie已经保存完成!")
}
}
}
gin框架session
package main
import (
"net/http"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 加入 session 中间件
store := cookie.NewStore([]byte("session_secret")) // 创建基于cookie的session存储
r.Use(sessions.Sessions("mySession", store)) // 使用session中间件,并指定session名称和存储方式
r.GET("/session", func(context *gin.Context) {
name := context.Query("name")
if len(name) <= 0 {
context.JSON(http.StatusOK, "数据错误")
return
}
sessionName := "session_" + name // 构造session名称
sessionValue := "session_value_" + name // 构造session值
session := sessions.Default(context) // 获取默认的session实例
sessionData := session.Get(sessionName)
if sessionData != sessionValue { // 如果session中不存在对应值,则为首次访问
session.Set(sessionName, sessionValue) // 设置 session
session.Save() // 保存 session
context.JSON(http.StatusOK, "首次访问,session已保存:"+sessionValue)
return
}
context.JSON(http.StatusOK, "访问成功,您的session是:"+sessionData.(string))
})
r.Run(":9090") // 启动HTTP服务器,监听9090端口
}
gin框架Https
- 申请证书并下载证书:https://keymanager.org/
package main
import (
"github.com/gin-gonic/gin"
"github.com/unrolled/secure"
"net/http"
)
// HttpRes 结构体用于定义HTTP响应格式
type HttpRes struct {
Code int `json:"code"` // 响应状态码
Result string `json:"result"` // 响应结果消息
}
func main() {
r := gin.Default() // 创建默认的gin路由引擎
// 使用HTTPS处理程序中间件
r.Use(httpsHandler())
// 定义GET路由"/https_test"
r.GET("/https_test", func(context *gin.Context) {
// 返回JSON格式的成功消息
context.JSON(http.StatusOK, HttpRes{
Code: http.StatusOK,
Result: "测试成功",
})
})
path := "D:/2-golang/CA/" // 证书路径
r.RunTLS(":9090", path+"ca.crt", path+"ca.key") // 使用TLS在9090端口运行服务器
}
// httpsHandler 返回一个处理HTTPS请求的中间件函数
func httpsHandler() gin.HandlerFunc {
return func(context *gin.Context) {
// 创建secure中间件实例
secureMiddleware := secure.New(secure.Options{
SSLRedirect: true, // 强制SSL重定向
STSSeconds: 1536000,
STSIncludeSubdomains: true,
STSPreload: true,
FrameDeny: true,
ContentTypeNosniff: true,
BrowserXssFilter: true,
})
// 处理HTTPS请求
err := secureMiddleware.Process(context.Writer, context.Request)
if err != nil {
// 如果出现错误,返回数据不安全的响应
context.AbortWithStatusJSON(http.StatusBadRequest, "数据不安全")
return
}
if status := context.Writer.Status(); status > 300 && status < 399 {
// 如果响应状态码是重定向类型,则终止请求
context.Abort()
return
}
context.Next() // 继续处理下一个中间件或路由处理函数
}
}
安全认证(一 静态方法)
package main
import (
"github.com/gin-gonic/gin"
"github.com/pjebs/restgate"
"net/http"
)
func main() {
r := gin.Default()
// 使用自定义的认证中间件
r.Use(authMiddleware())
// 定义一个需要认证的路由
r.GET("/auth1", func(context *gin.Context) {
// 返回验证通过的JSON响应
resData := struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data interface{} `json:"data"`
}{http.StatusOK, "验证通过", "OK"}
context.JSON(http.StatusOK, resData)
})
// 启动HTTP服务器并监听9090端口
r.Run(":9090")
}
// authMiddleware 是一个自定义的 Gin 中间件,用于身份验证
func authMiddleware() gin.HandlerFunc {
return func(context *gin.Context) {
// 创建一个 restgate 实例用于验证
gate := restgate.New(
"X-Auth-Key",
"X-Auth-Secret",
restgate.Static,
restgate.Config{
Key: []string{"admin"},
Secret: []string{"adminpw"},
HTTPSProtectionOff: true,
},
)
// 定义一个变量来标记是否调用了下一个中间件
nextCalled := false
// 定义一个适配器函数,用于在调用下一个中间件时设置标记
nextAdapter := func(http.ResponseWriter, *http.Request) {
nextCalled = true
context.Next()
}
// 使用 restgate 实例来验证请求,并传入适配器函数
gate.ServeHTTP(context.Writer, context.Request, nextAdapter)
// 如果没有调用下一个中间件,则返回未经授权的状态码
if !nextCalled {
context.AbortWithStatus(http.StatusUnauthorized)
}
}
}
产生警告:WARNING: HTTPS Protection is off. This is potentially insecure!
解决方案:按照 https 流程完成即可
安全认证(二 数据库方法)
package main
import (
"database/sql"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
"github.com/pjebs/restgate"
"net/http"
)
// main 函数是程序的入口点
func main() {
// 创建一个默认的 Gin 引擎实例
r := gin.Default()
// 使用自定义的认证中间件
r.Use(authMiddleware())
// 定义一个需要认证的路由
r.GET("/auth2", func(context *gin.Context) {
// 返回验证通过的JSON响应
resData := struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data string `json:"data"`
}{http.StatusOK, "验证通过", "OK"}
context.JSON(http.StatusOK, resData)
})
// 启动HTTP服务器并监听9090端口
r.Run(":9090")
}
var db *sql.DB
// init 函数用于初始化数据库连接
func init() {
// 初始化数据库连接
db, _ = SqlDB()
}
// SqlDB 函数用于连接数据库
func SqlDB() (*sql.DB, error) {
// 定义数据库连接参数
DB_TYPE := "mysql"
DB_HOST := "localhost"
DB_PORT := "3306"
DB_USER := "root"
DB_NAME := "api_secure"
DB_PASSWORD := "123456"
openString := DB_USER + ":" + DB_PASSWORD + "@tcp(" + DB_HOST + ":" + DB_PORT + ")/" + DB_NAME
// 打开数据库连接
db, err := sql.Open(DB_TYPE, openString)
return db, err
}
// authMiddleware 是一个自定义的 Gin 中间件,用于身份验证
func authMiddleware() gin.HandlerFunc {
return func(context *gin.Context) {
// 创建一个 restgate 实例用于验证
gate := restgate.New(
"X-Auth-Key",
"X-Auth-Secret",
restgate.Database,
restgate.Config{
DB: db,
Key: []string{"keys"},
Secret: []string{"secrets"},
TableName: "users",
HTTPSProtectionOff: true,
},
)
// 定义一个变量来标记是否调用了下一个中间件
nextCalled := false
// 定义一个适配器函数,用于在调用下一个中间件时设置标记
nextAdapter := func(http.ResponseWriter, *http.Request) {
nextCalled = true
context.Next()
}
// 使用 restgate 实例来验证请求,并传入适配器函数
gate.ServeHTTP(context.Writer, context.Request, nextAdapter)
// 如果没有调用下一个中间件,则返回未经授权的状态码
if !nextCalled {
context.AbortWithStatus(http.StatusUnauthorized)
}
}
}
日志框架logrus(一)
package main
import (
"fmt"
"github.com/gin-gonic/gin" // 引入 gin web框架
"github.com/sirupsen/logrus" // 引入 logrus 日志库
"net/http"
"os"
)
// 全局变量,创建 logrus 的新实例,用于记录日志
var log = logrus.New()
// initLogrus 初始化日志系统
func initLogrus() error {
log.Formatter = &logrus.JSONFormatter{} // 设置日志格式为 JSON
// 打开文件(没有就创建),以追加、创建和写入模式打开或创建日志文件
file, err := os.OpenFile("./gin_log.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
// 如果创建文件或者打开文件失败,打印错误提示并返回错误
fmt.Println("创建文件/打开文件失败!")
return err
}
// 设置 logrus 实例的输出为日志文件
log.Out = file
// 设置 gin web 框架的运行模式为 ReleaseMode,通常用于产品部署时减少日志输出和调试信息
gin.SetMode(gin.ReleaseMode)
// 将 gin 框架的默认日志写入到 logrus 设置的输出中去,使得 gin 框架日志与业务日志同步
gin.DefaultWriter = log.Out
// 设置日志级别为 InfoLevel,此级别下,只有 Info 及以上级别的日志会被输出
log.Level = logrus.InfoLevel
return nil
}
func main() {
// 初始化 logrus 日志设置
err := initLogrus()
if err != nil {
// 初始化失败,输出错误信息后返回
fmt.Println(err)
return
}
// 创建一个默认的 gin 路由器实例
r := gin.Default()
// 响应 GET 请求,路径为 "/logrus",后面的匿名函数为处理该路径请求的回调函数
r.GET("/logrus", func(context *gin.Context) {
// 使用 logrus 记录请求的相关信息
log.WithFields(logrus.Fields{
"url": context.Request.RequestURI, // 请求的 URL
"method": context.Request.Method, // 请求的方法,如 GET、POST 等
"params": context.Query("name"), // 请求的查询参数
"IP": context.ClientIP(), // 发起请求的客户端 IP
}).Info() // 将这些字段信息打印为 Info 级别的日志
// 定义响应数据结构,并初始化值
resData := struct {
Code int `json:"code"` // 响应状态码
Msg string `json:"msg"` // 响应信息
Data interface{} `json:"data"` // 响应数据
}{http.StatusOK, "响应成功", "OK"}
// 使用 JSON 格式响应请求
context.JSON(http.StatusOK, resData)
})
// 启动监听,并在 9090 端口上运行
r.Run(":9090")
}
日志框架logrus(二)
package main
import (
"fmt"
// 引入必要的包
// Gin框架用于Web服务器的构建和请求的处理
"github.com/gin-gonic/gin"
// file-rotatelogs用于日志文件的自动分割
"github.com/lestrrat-go/file-rotatelogs"
// lfshook用于为logrus提供钩子功能,根据日志级别输出到不同的目的地
"github.com/rifflock/lfshook"
// logrus是Go的一个结构化日志库,提供了比标准库更丰富的日志功能
"github.com/sirupsen/logrus"
"net/http"
"os"
"path"
"time"
)
// 定义日志文件的路径和名称
var (
logFilePath = "./"
logFileName = "system.log"
)
func main() {
// 创建Gin的路由器实例
r := gin.Default()
// 添加自定义的日志中间件
r.Use(logMiddleware())
// 处理GET请求,路径为"/logrus"
r.GET("/logrus", func(context *gin.Context) {
// 以JSON格式响应请求
context.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "响应成功",
"data": "OK",
})
})
// 在9090端口上启动服务器
r.Run(":9090")
}
// logMiddleware返回一个实现了Gin的中间件HandlerFunc的函数
// 用来为每个请求记录日志
func logMiddleware() gin.HandlerFunc {
// 拼接日志文件的完整路径
fileName := path.Join(logFilePath, logFileName)
// 尝试打开或创建日志文件
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println(err)
}
// 创建logrus的logger
logger := logrus.New()
// 设置日志级别为DebugLevel
logger.SetLevel(logrus.DebugLevel)
// 设置日志输出到上面打开的文件
logger.Out = file
// 配置日志文件的分割策略
logWriter, err := rotatelogs.New(
fileName+".%Y%m%d.log",
rotatelogs.WithLinkName(fileName), // 设置软连接指向最新的日志文件
rotatelogs.WithMaxAge(7*24*time.Hour), // 设置日志文件的最大保存时间
rotatelogs.WithRotationTime(1*time.Hour)) // 设置日志分割的时间间隔
// 定义将日志写入到分割的日志文件中
writerMap := lfshook.WriterMap{
logrus.InfoLevel: logWriter, // 根据日志级别使用不同的输出
logrus.FatalLevel: logWriter,
logrus.DebugLevel: logWriter,
logrus.WarnLevel: logWriter,
logrus.ErrorLevel: logWriter,
logrus.PanicLevel: logWriter,
}
// 为logrus添加一个钩子,使用自定义的分割策略和JSON格式器
logger.AddHook(lfshook.NewHook(writerMap, &logrus.JSONFormatter{
TimestampFormat: "2024-05-06 15:49:50", // 自定义的时间戳格式
}))
// 返回一个Gin的中间件,记录每次请求的详细信息
return func(context *gin.Context) {
context.Next() // 处理请求的其它部分
// 获取请求的一些基本信息
method := context.Request.Method
uri := context.Request.RequestURI
code := context.Writer.Status()
ip := context.ClientIP()
// 使用这些信息打印日志
logger.WithFields(logrus.Fields{
"code": code,
"ip": ip,
"method": method,
"uri": uri,
}).Info() // 记录日志
}
}
原生数据库使用
导入模块:go get -u github.com/go-sql-driver/mysql
安装数据库可能遇到的问题:(网上的方法基本可以解决)
ERROR 1045 (28000): Access denied for user ‘-root’@‘localhost’ (using password: YES)
ERROR 2003 (HY000): Can’t connect to MySQL server on ‘localhost’ (10061)
// 导包内容
package main
import (
"database/sql" // 导入sql包处理数据库操作
"fmt" // 导入fmt包进行格式化输出和错误输出
"github.com/gin-gonic/gin" // 导入gin包快速构建REST API
_ "github.com/go-sql-driver/mysql" // 导入MySQL驱动
"net/http" // 导入http包处理HTTP请求
)
注意别漏了:_ “github.com/go-sql-driver/mysql” // 导入MySQL驱动
var sqlDb *sql.DB // 声明一个sql.DB指针,用于后续的数据库操作
var sqlResponse SqlResponse // sqlResponse是全局的用于封装http返回的结构体
/** 请求结构体 和 响应结构体 */
// SqlUser结构体映射数据库中的user表
type SqlUser struct {
Name string `json:"name"` // 用户名
Age int `json:"age"` // 年龄
Address string `json:"address"` // 地址
}
// SqlResponse结构体用于封装http响应数据
type SqlResponse struct {
Code int `json:"code"` // 响应状态码
Message string `json:"message"` // 响应消息
Data interface{} `json:"data"` // 响应数据
}
// init函数用于初始化数据库
func init() {
// MySQL连接字符串
sqlStr := "root:123456@tcp(127.0.0.1:3306)/testdb?charset=utf8&parseTime=true&loc=Local"
var err error
// 打开MySQL连接
sqlDb, err = sql.Open("mysql", sqlStr)
if err != nil {
// 如果连接出错,则输出错误并返回
fmt.Println("数据库打开出现了问题:", err)
return
}
// 测试与数据库的连接是否存活
err = sqlDb.Ping()
if err != nil {
fmt.Println("数据库打开出现了问题:", err)
return
}
}
// 主函数
func main() {
r := gin.Default()
r.POST("/sql/insert", insertData)
r.GET("/sql/read", readData)
r.GET("/sql/readAll", readAllData)
r.PUT("/sql/update", updateData)
r.DELETE("/sql/del", delData)
r.Run(":9090")
}
// insertData函数处理插入数据的请求
func insertData(context *gin.Context) {
var u SqlUser
// 绑定请求中的JSON数据到u结构体
err := context.Bind(&u)
if err != nil {
// 如果绑定出错,设置响应结构体并返回JSON响应
sqlResponse.Code = http.StatusBadRequest
sqlResponse.Message = "参数错误"
sqlResponse.Data = "error"
context.JSON(http.StatusOK, sqlResponse)
return
}
// 定义插入SQL语句
sqlStr := "insert into user(name, age, address) values (?,?,?)"
// 执行插入操作
ret, err := sqlDb.Exec(sqlStr, u.Name, u.Age, u.Address)
if err != nil {
// 如果插入出错,设置响应结构体并返回JSON响应
fmt.Println(err)
sqlResponse.Code = http.StatusBadRequest
sqlResponse.Message = "写入失败"
sqlResponse.Data = "error"
context.JSON(http.StatusOK, sqlResponse)
return
}
// 插入成功,设置响应结构体
sqlResponse.Code = http.StatusOK
sqlResponse.Message = "写入成功"
sqlResponse.Data = "OK"
// 返回JSON响应
context.JSON(http.StatusOK, sqlResponse)
// 打印插入的ID
fmt.Println(ret.LastInsertId())
}
// readData函数处理读取数据的请求:单条数据
func readData(context *gin.Context) {
name := context.Query("name") // 从查询参数获取name
// 定义查询SQL语句
sqlStr := "select age,address from user where name=?"
var u SqlUser
// 执行查询,并扫描结果到u结构体
err := sqlDb.QueryRow(sqlStr, name).Scan(&u.Age, &u.Address)
if err != nil {
// 如果查询出错,设置响应结构体并返回JSON响应
fmt.Println(err)
sqlResponse.Code = http.StatusBadRequest
sqlResponse.Message = "读取失败"
sqlResponse.Data = "error"
context.JSON(http.StatusOK, sqlResponse)
return
}
// 查询成功,填充姓名,并设置响应结构体
u.Name = name
sqlResponse.Code = http.StatusOK
sqlResponse.Message = "读取成功"
sqlResponse.Data = u
// 返回JSON响应
context.JSON(http.StatusOK, sqlResponse)
}
func readAllData(context *gin.Context) {
// 从Http请求中获取地址参数(address)
address := context.Query("address")
// 定义SQL查询语句,根据特定地址(address)查询与之匹配的所有用户名称(name)和用户年龄(age)
sqlStr := "select name, age from user where address=?"
// 使用预定义的sqlDb对象执行刚刚定义的SQL查询语句
rows, err := sqlDb.Query(sqlStr, address)
// 如果在执行SQL查询语句过程中出现错误,将错误信息打印到控制台,并向HTTP响应中写入相应的错误信息,然后退出当前函数
if err != nil {
fmt.Println(err)
sqlResponse.Code = http.StatusBadRequest
sqlResponse.Message = "读取失败"
sqlResponse.Data = "error"
context.JSON(http.StatusOK, sqlResponse)
return
}
// 定时关闭数据库连接
defer rows.Close()
// 定义一个空切片,用来存储所有查询到的用户数据
resUser := make([]SqlUser, 0)
// 遍历数据库查询结果集rows,提取所有的数据并添加到之前定义的用于存储用户数据的切片中
for rows.Next() {
var userTemp SqlUser
// 将从结果集中获取到的每一行数据相应的字段(name, age)提取出来,赋值给用户结构体(userTemp)对应的字段
rows.Scan(&userTemp.Name, &userTemp.Age)
// 将地址字段值设置为查询参数
userTemp.Address = address
// 将该用户结构体添加到用户切片中
resUser = append(resUser, userTemp)
}
// 设置HTTP响应的状态码、消息和数据内容,然后将其写入到HTTP响应中。这里写入的数据内容就是查询到的所有用户数据
sqlResponse.Code = http.StatusOK
sqlResponse.Message = "读取成功"
sqlResponse.Data = resUser
context.JSON(http.StatusOK, sqlResponse)
}
func updateData(context *gin.Context) {
// 定义一个SqlUser类型的变量u,用于存储请求中的用户数据
var u SqlUser
// 定义一个整型变量count,用于存储数据库查询返回的计数结果
var count int
// 使用context.Bind()方法从HTTP请求体中提取数据并绑定到变量u中
err := context.Bind(&u)
// 如果在数据绑定过程中发生错误,则向客户端发送参数错误的响应
if err != nil {
sqlResponse.Code = http.StatusBadRequest
sqlResponse.Message = "参数错误"
sqlResponse.Data = "error"
context.JSON(http.StatusOK, sqlResponse)
return
}
// 定义SQL查询字符串,用于检查具有特定名称的用户是否存在于数据库中
sqlStr := "select count(*) from user where name=?"
// 执行SQL查询,将查询结果(即用户的数量)存储于变量count中
err = sqlDb.QueryRow(sqlStr, u.Name).Scan(&count)
// 如果查询结果显示用户数量为0或查询时发生错误,则向客户端发送数据不存在的响应
if count <= 0 || err != nil {
sqlResponse.Code = http.StatusBadRequest
sqlResponse.Message = "更新的数据不存在"
sqlResponse.Data = "error"
context.JSON(http.StatusOK, sqlResponse)
return
}
// 定义SQL更新字符串,用于更新用户的年龄(age)和地址(address)
upStr := "update user set age=?, address=? where name=?"
// 执行SQL更新操作
ret, err := sqlDb.Exec(upStr, u.Age, u.Address, u.Name)
// 如果在执行更新操作时发生错误,则向客户端发送更新失败的响应
if err != nil {
fmt.Println(err)
sqlResponse.Code = http.StatusBadRequest
sqlResponse.Message = "更新失败"
sqlResponse.Data = "error"
context.JSON(http.StatusOK, sqlResponse)
return
}
// 如果更新操作成功,向客户端发送更新成功的响应
sqlResponse.Code = http.StatusOK
sqlResponse.Message = "更新成功"
sqlResponse.Data = "OK"
context.JSON(http.StatusOK, sqlResponse)
// 打印到控制台SQL操作返回的结果,这通常用于调试
// 注意:这里的LastInsertId()在更新操作中可能不会返回有意义的值,需要注意其适用场景
fmt.Println(ret.LastInsertId())
}
func delData(context *gin.Context) {
// 从HTTP请求的查询参数中获取需要删除的用户的名称
name := context.Query("name")
// 定义一个整型变量count,用于存储数据库查询返回的计数结果
var count int
// 定义SQL查询字符串,用于检查具有特定名称的用户是否存在于数据库中
sqlStr := "select count(*) from user where name=?"
// 执行SQL查询,将查询结果(即用户的数量)存储于变量count中
err := sqlDb.QueryRow(sqlStr, name).Scan(&count)
// 如果查询结果显示用户数量为0或查询时发生错误,则向客户端发送数据不存在的响应
if count <= 0 || err != nil {
sqlResponse.Code = http.StatusBadRequest
sqlResponse.Message = "删除的数据不存在"
sqlResponse.Data = "error"
context.JSON(http.StatusOK, sqlResponse)
return
}
// 定义SQL删除字符串,用于从数据库中删除具有特定名称的用户
delStr := "delete from user where name=?"
// 执行SQL删除操作
ret, err := sqlDb.Exec(delStr, name)
// 如果在执行删除操作时发生错误,则向客户端发送删除失败的响应
if err != nil {
sqlResponse.Code = http.StatusBadRequest
sqlResponse.Message = "删除的数据不存在"
sqlResponse.Data = "error"
context.JSON(http.StatusOK, sqlResponse)
return
}
// 如果删除成功,向客户端发送删除成功的响应
sqlResponse.Code = http.StatusOK
sqlResponse.Message = "删除成功"
sqlResponse.Data = "OK"
context.JSON(http.StatusOK, sqlResponse)
// 打印到控制台SQL操作返回的结果,这通常用于调试
// 注意:这里的LastInsertId()在删除操作中可能不会返回有意义的值,因为它更适用于插入操作
fmt.Println(ret.LastInsertId())
}
xorm框架
package main
import (
"fmt"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql" // 导入MySQL驱动
"github.com/go-xorm/xorm"
"net/http"
"time"
)
var x *xorm.Engine
var xormResponse XormResponse
type Stu struct {
Id int64 `xorm:"pk autoincr" json:"id"`
StuNum string `xorm:"unique" json:"stu_num"`
Name string `json:"name"`
Age int `json:"age"`
Created time.Time `xorm:"created" json:"created"`
Updated time.Time `xorm:"updated" json:"updated"`
}
type XormResponse struct {
Code int `json:"code"` // 响应状态码
Message string `json:"message"` // 响应消息
Data interface{} `json:"data"` // 响应数据
}
func init() {
sqlStr := "root:123456@tcp(127.0.0.1:3306)/xorm?charset=utf8&parseTime=true&loc=Local"
var err error
x, err = xorm.NewEngine("mysql", sqlStr)
if err != nil {
fmt.Println("数据库连接失败", err)
return
}
err = x.Sync(new(Stu))
if err != nil {
fmt.Println("数据库同步错误", err)
return
}
fmt.Println("数据库初始化成功", err)
}
func main() {
r := gin.Default()
r.POST("/xorm/insert", insertData)
r.GET("/xorm/read", readData)
r.GET("/xorm/mulread", mulReadData)
r.PUT("/xorm/update", xormUpdateData)
r.DELETE("/xorm/del", delData)
r.Run(":9090")
}
func insertData(context *gin.Context) {
var s Stu
err := context.Bind(&s)
if err != nil {
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "参数错误"
xormResponse.Data = "error"
context.JSON(http.StatusOK, xormResponse)
return
}
affected, err := x.Insert(s)
if affected <= 0 || err != nil {
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "写入失败"
xormResponse.Data = "error"
context.JSON(http.StatusOK, xormResponse)
return
}
xormResponse.Code = http.StatusOK
xormResponse.Message = "写入成功"
xormResponse.Data = "OK"
context.JSON(http.StatusOK, xormResponse)
}
func readData(context *gin.Context) {
stuNum := context.Query("stu_num")
var stus []Stu
err := x.Where("stu_num=?", stuNum).Find(&stus)
if err != nil {
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "查询错误"
xormResponse.Data = "error"
context.JSON(http.StatusOK, xormResponse)
return
}
xormResponse.Code = http.StatusOK
xormResponse.Message = "查询成功"
xormResponse.Data = stus
context.JSON(http.StatusOK, xormResponse)
}
func mulReadData(context *gin.Context) {
name := context.Query("name")
var stus []Stu
err := x.Where("name=?", name).And("age>20").Limit(10, 0).Asc("age").Find(&stus)
if err != nil {
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "查询错误"
xormResponse.Data = "error"
context.JSON(http.StatusOK, xormResponse)
return
}
xormResponse.Code = http.StatusOK
xormResponse.Message = "查询成功"
xormResponse.Data = stus
context.JSON(http.StatusOK, xormResponse)
}
func xormUpdateData(context *gin.Context) {
var s Stu
err := context.Bind(&s)
if err != nil {
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "参数错误"
xormResponse.Data = "error"
context.JSON(http.StatusOK, xormResponse)
return
}
var stus []Stu
err = x.Where("stu_num=?", s.StuNum).Find(&stus)
if err != nil || len(stus) <= 0 {
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "数据不存在"
xormResponse.Data = "error"
context.JSON(http.StatusOK, xormResponse)
return
}
affected, err := x.Where("stu_num=?", s.StuNum).Update(&Stu{Name: s.Name, Age: s.Age})
if err != nil || affected <= 0 {
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "修改失败"
xormResponse.Data = "error"
context.JSON(http.StatusOK, xormResponse)
return
}
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "修改成功"
xormResponse.Data = "OK"
context.JSON(http.StatusOK, xormResponse)
}
func delData(context *gin.Context) {
stuNum := context.Query("stu_num")
var stus []Stu
err := x.Where("name=?", stuNum).Find(&stus)
if err != nil {
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "没有此条数据"
xormResponse.Data = "error"
context.JSON(http.StatusOK, xormResponse)
return
}
affected, err := x.Where("stu_num=?", stuNum).Delete(&Stu{})
if err != nil || affected <= 0 {
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "删除失败"
xormResponse.Data = "error"
context.JSON(http.StatusOK, xormResponse)
return
}
xormResponse.Code = http.StatusBadRequest
xormResponse.Message = "删除成功"
xormResponse.Data = "OK"
context.JSON(http.StatusOK, xormResponse)
}
gorm框架
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"net/http"
"time"
)
type Product struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
Number string `gorm:"unique" json:"number"`
Category string `gorm:"type:varchar(256);not null" json:"category"`
Name string `gorm:"type:varchar(20);not null" json:"name"`
MadeIn string `gorm:"type:varchar(128);not null" json:"madeIn"`
ProductionTime time.Time `json:"production_time"`
}
type GormResponse struct {
Code int `json:"code"` // 响应状态码
Message string `json:"message"` // 响应消息
Data interface{} `json:"data"` // 响应数据
}
var gormResponse GormResponse
var gormDB *gorm.DB
func init() {
var err error
sqlStr := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=true&loc=Local"
gormDB, err = gorm.Open(mysql.Open(sqlStr), &gorm.Config{})
if err != nil {
fmt.Println("数据库连接失败", err)
return
}
}
func main() {
r := gin.Default()
r.POST("/gorm/insert", insetData)
r.GET("/gorm/read", readData)
r.GET("/gorm/readAll", readAllData)
r.PUT("/gorm/update", updateData)
r.DELETE("/gorm/del", delData)
r.Run(":9090")
}
func errorTouch(context *gin.Context) {
defer func() {
err := recover()
if err != nil {
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "错误"
gormResponse.Data = err
context.JSON(http.StatusOK, gormResponse)
}
}()
}
func insetData(context *gin.Context) {
errorTouch(context)
var p Product
err := context.Bind(&p)
if err != nil {
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "参数错误"
gormResponse.Data = err
context.JSON(http.StatusOK, gormResponse)
return
}
tx := gormDB.Create(&p)
if tx.RowsAffected <= 0 {
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "写入失败"
gormResponse.Data = "error"
context.JSON(http.StatusBadRequest, gormResponse)
return
}
gormResponse.Code = http.StatusOK
gormResponse.Message = "写入成功"
gormResponse.Data = "ok"
context.JSON(http.StatusOK, gormResponse)
}
func readData(context *gin.Context) {
errorTouch(context)
number := context.Query("number")
product := Product{}
tx := gormDB.Where("number=?", number).First(&product)
if tx.Error != nil {
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "查询错误"
gormResponse.Data = tx.Error
context.JSON(http.StatusOK, gormResponse)
return
}
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "查询成功"
gormResponse.Data = product
context.JSON(http.StatusOK, gormResponse)
}
func readAllData(context *gin.Context) {
category := context.Query("category")
products := make([]Product, 10)
tx := gormDB.Where("category=?", category).Find(&products).Limit(10)
if tx.Error != nil {
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "查询错误"
gormResponse.Data = tx.Error
context.JSON(http.StatusOK, gormResponse)
return
}
gormResponse.Code = http.StatusOK
gormResponse.Message = "查询成功"
gormResponse.Data = products
context.JSON(http.StatusOK, gormResponse)
}
func updateData(context *gin.Context) {
errorTouch(context)
var p Product
err := context.Bind(&p)
if err != nil {
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "参数错误"
gormResponse.Data = err
context.JSON(http.StatusOK, gormResponse)
return
}
var count int64
gormDB.Model(&Product{}).Where("number=?", p.Number).Count(&count)
if count <= 0 {
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "数据不存在"
gormResponse.Data = "error"
context.JSON(http.StatusOK, gormResponse)
return
}
tx := gormDB.Model(&Product{}).Where("number=?", p.Number).Updates(&p)
if tx.RowsAffected <= 0 {
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "写入失败"
gormResponse.Data = "error"
context.JSON(http.StatusOK, gormResponse)
return
}
gormResponse.Code = http.StatusOK
gormResponse.Message = "更新成功"
gormResponse.Data = "ok"
context.JSON(http.StatusOK, gormResponse)
}
func delData(context *gin.Context) {
errorTouch(context)
number := context.Query("number")
var count int64
gormDB.Model(&Product{}).Where("number=?", number).Count(&count)
if count <= 0 {
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "数据不存在"
gormResponse.Data = "error"
context.JSON(http.StatusOK, gormResponse)
return
}
tx := gormDB.Where("number=?", number).Delete(&Product{})
if tx.RowsAffected <= 0 {
gormResponse.Code = http.StatusBadRequest
gormResponse.Message = "删除失败"
gormResponse.Data = "error"
context.JSON(http.StatusOK, gormResponse)
return
}
gormResponse.Code = http.StatusOK
gormResponse.Message = "删除成功"
gormResponse.Data = "ok"
context.JSON(http.StatusOK, gormResponse)
}
HMAC 签名
package main
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"net/http"
"strings"
"time"
)
type HmacUser struct {
Id string `json:"id"`
Name string `json:"name"`
Telephone string `json:"telephone"`
Password string `json:"password"`
}
type MyClaims struct {
UserId string
jwt.StandardClaims
}
var jwtKey = []byte("a_secret_key")
func main() {
r := gin.Default()
r.POST("/getToken1", func(context *gin.Context) {
var u HmacUser
context.Bind(&u)
token, err := hmacReleaseToken(u)
if err != nil {
context.JSON(http.StatusInternalServerError, err)
}
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "token分发成功",
"data": token,
})
})
r.POST("/checkToken1", hmacAuthMiddleware(), func(context *gin.Context) {
context.JSON(http.StatusOK, "验证成功")
})
r.Run(":9090")
}
func hmacAuthMiddleware() gin.HandlerFunc {
return func(context *gin.Context) {
auth := "fanfan"
tokenString := context.GetHeader("Authorization")
if tokenString == "" || !strings.HasPrefix(tokenString, auth+":") {
context.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "前缀错误"})
context.Abort()
return
}
index := strings.Index(tokenString, auth+":")
tokenString = tokenString[index+len(auth)+1:]
token, claims, err := hmacParseToken(tokenString)
if err != nil || !token.Valid {
context.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "证书无效"})
context.Abort()
return
}
var u HmacUser
context.Bind(&u)
if u.Id != claims.UserId {
context.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "用户不存在"})
context.Abort()
return
}
context.Next()
}
}
func hmacParseToken(tokenString string) (*jwt.Token, *MyClaims, error) {
claims := &MyClaims{}
token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
return token, claims, err
}
func hmacReleaseToken(u HmacUser) (string, error) {
expirationTime := time.Now().Add(7 * 24 * time.Hour)
claims := &MyClaims{
UserId: u.Id,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(),
IssuedAt: time.Now().Unix(),
Issuer: "fanfan",
Subject: "user token",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(jwtKey)
if err != nil {
return "", err
}
return tokenString, nil
}
RSA签名
rsa签名生成公钥、私钥:https://www.metools.info/code/c80.html
package main
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"io/ioutil"
"net/http"
"strings"
"time"
)
type RsaUser struct {
Id string `json:"id"`
Name string `json:"name"`
Telephone string `json:"telephone"`
Password string `json:"password"`
}
type RasClaims struct {
UserId string `json:"user_id"`
jwt.StandardClaims
}
var (
resPrivateKey []byte
resPublicKey []byte
err2_1, err2_2 error
)
func init() {
resPrivateKey, err2_1 = ioutil.ReadFile("./token/private.pem")
resPublicKey, err2_2 = ioutil.ReadFile("./token/public.pem")
if err2_1 != nil || err2_2 != nil {
panic(fmt.Sprintf("打开密钥文件错误:%s,%s", err2_1, err2_2))
}
}
func main() {
r := gin.Default()
r.POST("/getToken2", func(context *gin.Context) {
u := RsaUser{}
err := context.Bind(&u)
if err != nil {
context.JSON(http.StatusBadRequest, "参数错误")
return
}
token, err := resaReleaseToken(u)
if err != nil {
context.JSON(http.StatusBadRequest, "生成token错误")
return
}
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "token分发成功",
"data": token,
})
})
r.POST("/checkToken2", rsaTokenMiddle(), func(context *gin.Context) {
context.JSON(http.StatusOK, "验证成功")
})
r.Run(":9090")
}
func rsaTokenMiddle() gin.HandlerFunc {
return func(context *gin.Context) {
auth := "fanfan"
tokenString := context.GetHeader("Authorization")
if tokenString == "" || !strings.HasPrefix(tokenString, auth+":") {
context.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "无效的token"})
context.Abort()
return
}
index := strings.Index(tokenString, auth+":")
tokenString = tokenString[index+len(auth)+1:]
claims, err := rsaJwtTokenRead(tokenString)
if err != nil {
context.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "证书无效"})
context.Abort()
return
}
claimsValue := claims.(jwt.MapClaims)
if claimsValue["user_id"] == nil {
context.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "用户不存在"})
context.Abort()
return
}
var u RsaUser
context.Bind(&u)
if u.Id != claimsValue["user_id"].(string) {
context.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "用户不存在"})
context.Abort()
return
}
context.Next()
}
}
func rsaJwtTokenRead(tokenString string) (interface{}, error) {
pem, err := jwt.ParseRSAPublicKeyFromPEM(resPublicKey)
if err != nil {
return nil, err
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, OK := token.Method.(*jwt.SigningMethodRSA); !OK {
return nil, fmt.Errorf("解析方法错误")
}
return pem, err
})
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
return claims, err
}
return nil, err
}
func resaReleaseToken(u RsaUser) (interface{}, error) {
tokenGen, err := rasJwtTokenGen(u.Id)
return tokenGen, err
}
func rasJwtTokenGen(id string) (interface{}, error) {
private, err := jwt.ParseRSAPrivateKeyFromPEM(resPrivateKey)
if err != nil {
return nil, err
}
claims := &RasClaims{
UserId: id,
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(7 * 24 * time.Hour).Unix(),
Issuer: "fanfan",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
signedString, err := token.SignedString(private)
return signedString, err
}
ECDSA签名
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"errors"
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"net/http"
"strings"
"time"
)
type EcdsaUser struct {
Id string `json:"id"`
Name string `json:"name"`
Telephone string `json:"telephone"`
Password string `json:"password"`
}
type EcdsaClaims struct {
UserId string `json:"user_id"`
jwt.StandardClaims
}
var (
err3 error
eccPrivateKey *ecdsa.PrivateKey
eccPublicKey *ecdsa.PublicKey
)
func init() {
eccPrivateKey, eccPublicKey, err3 = getEcdsaKey(2)
if err3 != nil {
panic(err3)
return
}
}
func ecdsaReleaseToken(u EcdsaUser) (interface{}, error) {
claims := &EcdsaClaims{
UserId: u.Id,
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(7 * 24 * time.Hour).Unix(),
Issuer: "fanfan",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
signedString, err := token.SignedString(eccPrivateKey)
return signedString, err
}
func main() {
r := gin.Default()
r.POST("/getToken3", func(context *gin.Context) {
var u EcdsaUser
err := context.Bind(&u)
if err != nil {
context.JSON(http.StatusInternalServerError, "参数错误")
}
token, err := ecdsaReleaseToken(u)
if err != nil {
context.JSON(http.StatusInternalServerError, err)
}
context.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "token分发成功",
"data": token,
})
})
r.POST("/checkToken3", ecdsaTokenMiddleware(), func(context *gin.Context) {
context.JSON(http.StatusOK, "验证成功")
})
r.Run(":9090")
}
func ecdsaTokenMiddleware() gin.HandlerFunc {
return func(context *gin.Context) {
auth := "fanfan"
tokenString := context.GetHeader("Authorization")
if tokenString == "" || !strings.HasPrefix(tokenString, auth+":") {
context.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "无效的token"})
context.Abort()
return
}
index := strings.Index(tokenString, auth+":")
tokenString = tokenString[index+len(auth)+1:]
claims, err := ecdsaJwtTokenRead(tokenString)
if err != nil {
context.AbortWithStatusJSON(http.StatusUnauthorized, err)
return
}
claimsValue := claims.(jwt.MapClaims)
if claimsValue["user_id"] == nil {
context.AbortWithStatusJSON(http.StatusUnauthorized, "id不存在")
return
}
var u EcdsaUser
context.Bind(&u)
if u.Id != claimsValue["user_id"] {
context.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "用户不存在"})
context.Abort()
return
}
context.Next()
}
}
func ecdsaJwtTokenRead(tokenString string) (interface{}, error) {
myToken, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
return nil, fmt.Errorf("无效的签名方法")
}
return eccPublicKey, nil
})
if claims, ok := myToken.Claims.(jwt.MapClaims); ok && myToken.Valid {
return claims, nil
}
return nil, err
}
func getEcdsaKey(keyType int) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) {
var err error
var prk *ecdsa.PrivateKey
var pub *ecdsa.PublicKey
var curve elliptic.Curve
switch keyType {
case 1:
curve = elliptic.P224()
case 2:
curve = elliptic.P256()
case 3:
curve = elliptic.P384()
case 4:
curve = elliptic.P521()
default:
err = errors.New("输入签名 key 类型错误!")
return nil, nil, err
}
prk, err = ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, nil, err
}
pub = &prk.PublicKey
return prk, pub, err
}