#45 Gin 框架自学: ORM [编辑中]
GinNotes Golang GORM Gin 2021-05-06:) 本文正在编辑中,暂时不提供浏览...
#44 Gin 框架自学: 基础
GinNotes Golang Gin 2021-05-06先列一下目前最火的几个 Go 语言框架:
- gin
- beego 国人开发
- iris The fastest HTTP/2 Go Web Framework.
自称是 ExpressJS 和 Laravel 的继承者。
- echo High performance, minimalist Go web framework
- go-micro Microservice framework for Go
- fiber
- kratos A Go framework for microservices. B 站出品
- revel/revel
- go-zero 国人开发(七牛)
- martini Classy web framework for Go
- GoFrame Classy web framework for Go 国人开发
Gin
Gin 相比 Beego 非常轻量级,不带 ORM,不支持 Session,正则路由都不支持,需要自行实现。
最新版本 56 个 文件, 算上注释,大概七千行代码。
$ find . -name "*.go" -not -path "*_test.go" | cat | wc -l
56
$ find . -name "*.go" -not -path "*_test.go" | xargs cat | wc -l
6910
Tag | CreatedAt | ReleasedAt |
---|---|---|
v1.9.0 | 2023-02-21 | 2023-02-21 |
v1.8.2 | 2022-12-22 | 2022-12-22 |
v1.8.1 | 2022-06-06 | 2022-06-06 |
v1.8.0 | 2022-05-30 | 2022-05-30 |
v1.7.7 | 2021-11-24 | 2021-11-24 |
v1.7.6 | 2021-08-03 | 2021-11-23 |
v1.7.4 | 2021-08-03 | 2021-08-15 |
v1.7.3 | 2021-08-03 | 2021-08-03 |
v1.7.2 | 2021-05-21 | 2021-05-21 |
v1.7.1 | 2021-04-08 | 2021-04-08 |
v1.7.0 | 2021-04-08 | 2021-04-08 |
v1.6.3 | 2020-05-03 | 2020-05-03 |
v1.6.2 | 2020-03-27 | 2020-03-27 |
v1.6.1 | 2020-03-23 | 2020-03-23 |
v1.6.0 | 2020-03-22 | 2020-03-22 |
v1.5.0 | 2019-11-24 | 2019-11-28 |
v1.4.0 | 2019-05-07 | 2019-05-08 |
v1.3.0 | 2018-08-14 | 2018-08-14 |
v1.2 | 2017-07-02 | 2017-07-02 |
v1.1.4 | 2016-12-04 | 2016-12-05 |
v1.1.3 | 2016-12-03 | 2016-12-04 |
HTTP 参数
// c: *gin.Context
// func (c *gin.Context) {
// balabala...
// }
c.Request.URL.Query()
c.Param(key string) string // 路径参数
c.Params.ByName(key)
c.Query(key string) string // 查询参数
c.Request.URL.Query().Get(key)
c.GetQuery(key)
c.QueryMap(key string) map[string][]string
c.GetQueryMap(key)
c.QueryArray(key string) []string
c.GetQuery(key string) (string, bool)
c.GetQueryArray(key) 的第 0 个元素
c.GetQueryMap(key string) (map[string]string, bool)
c.GetQueryArray(key string) (values []string, ok bool)
c.PostForm(key string) string
c.PostFormMap(key string) map[string][]string
c.PostFormArray(key string) []string
c.GetPostForm(key string) (string, bool)
c.GetPostFormMap(key string) (map[string]string, bool)
c.GetPostFormArray(key string) (values []string, ok bool)
c.FormFile(key string) (*multipart.FileHeader, error)
c.DefaultQuery(key string, defaultValue string) string
c.DefaultPostForm(key string, defaultValue string) string
- 2021/05/06, Gin 框架自学: 基础
- 2021/05/06, Gin 框架自学: ORM
- 2021/05/07, Gin 框架自学: 实战
- 2021/08/17, Gin 源码阅读
- 2022/04/03, Gin + Vue3 (ElementPlus) 开发一个通用管理后台
快速启动
package main
import (
"io"
"net/http"
"os"
"path/filepath"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 处理 GET 请求中的 URL 参数
r.GET("/hello", func(c *gin.Context) {
name := c.Query("name")
c.String(http.StatusOK, "Hello %s!", name)
})
// 处理带路径参数的 GET 请求
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.String(http.StatusOK, "User ID: %s", id)
})
// 处理 POST 请求中的 JSON 请求体
r.POST("/data", func(c *gin.Context) {
var json struct {
Data string `json:"data"`
}
c.BindJSON(&json)
c.JSON(http.StatusOK, gin.H{"message": "Received data: " + json.Data})
})
// 处理文件上传
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.String(http.StatusBadRequest, "File upload failed: %s", err.Error())
return
}
// 将上传的文件保存到服务器上
dst := filepath.Join("./uploads", file.Filename)
if err := c.SaveUploadedFile(file, dst); err != nil {
c.String(http.StatusInternalServerError, "File save failed: %s", err.Error())
return
}
c.String(http.StatusOK, "File uploaded successfully")
})
// 处理返回文件
r.GET("/download", func(c *gin.Context) {
filename := "example.txt"
filepath := "./downloads/" + filename
// 打开文件
file, err := os.Open(filepath)
if err != nil {
c.String(http.StatusInternalServerError, "Failed to open file: %s", err.Error())
return
}
defer file.Close()
// 设置响应头
c.Header("Content-Disposition", "attachment; filename="+filename)
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Transfer-Encoding", "binary")
// 将文件内容写入响应主体
_, err = io.Copy(c.Writer, file)
if err != nil {
c.String(http.StatusInternalServerError, "Failed to write file: %s", err.Error())
}
})
r.Run(":8080")
}
路由
设置路由不存在的处理方法:
r.NoRoute(func(c *gin.Context) {
c.JSON(404, gin.H{"message": "page not found"})
})
限制请求大小:
r.MaxMultipartMemory = 10 << 20 // 10MB
// "github.com/gin-contrib/size"
r.Use(size.Check(size.MB * 10))
r.Use(requestSizeLimitMiddleware(10 << 20))
func requestSizeLimitMiddleware(maxSize int64) gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.ContentLength > maxSize {
c.AbortWithStatusJSON(413, gin.H{"message": "request entity too large"})
return
}
c.Next()
}
}
#43 Golang 自动过期 Map
Golang 2021-04-26type ExpiringMap struct {
m sync.Map
expiry time.Duration
}
func (e *ExpiringMap) set(key, value interface{}) {
e.m.Store(key, value)
go func() {
time.Sleep(e.expiry)
e.m.Delete(key)
}()
}
改进:定时清理
package main
import (
"sync"
"time"
)
type Item struct {
value interface{}
expiration int64
}
type ExpiringMap struct {
m sync.Mutex
items map[string]Item
timeout time.Duration
}
func NewExpiringMap(timeout time.Duration) *ExpiringMap {
em := &ExpiringMap{
items: make(map[string]Item),
timeout: timeout,
}
go em.cleanup()
return em
}
func (em *ExpiringMap) Set(key string, value interface{}) {
em.m.Lock()
defer em.m.Unlock()
em.items[key] = Item{
value: value,
expiration: time.Now().Add(em.timeout).UnixNano(),
}
}
func (em *ExpiringMap) Get(key string) (interface{}, bool) {
em.m.Lock()
defer em.m.Unlock()
item, found := em.items[key]
if !found {
return nil, false
}
if time.Now().UnixNano() > item.expiration {
return nil, false
}
return item.value, true
}
func (em *ExpiringMap) Delete(key string) {
em.m.Lock()
defer em.m.Unlock()
delete(em.items, key)
}
func (em *ExpiringMap) deleteExpiredKeys() {
em.m.Lock()
defer em.m.Unlock()
now := time.Now().UnixNano()
for key, item := range em.items {
if now < item.expiration {
break
}
delete(em.items, key)
}
}
func (em *ExpiringMap) cleanup() {
for {
time.Sleep(em.timeout) // 阻塞协程
em.deleteExpiredKeys()
}
}
func main() {
em := NewExpiringMap(time.Second * 10)
time.Sleep(time.Second * 15)
_, ok := em.Get("key1")
if !ok {
println("key1 is deleted")
}
_, ok = em.Get("key2")
if !ok {
println("key2 is deleted")
}
}
#42 fasthttp: 十倍速
Golang 2021-04-26https://github.com/valyala/fasthttp
fasthttp
package main
import (
"flag"
"fmt"
"log"
"github.com/valyala/fasthttp"
)
var (
addr = flag.String("addr", ":8080", "TCP address to listen to")
compress = flag.Bool("compress", false, "Whether to enable transparent response compression")
)
func main() {
flag.Parse()
h := requestHandler
if *compress {
h = fasthttp.CompressHandler(h)
}
if err := fasthttp.ListenAndServe(*addr, h); err != nil {
log.Fatalf("Error in ListenAndServe: %v", err)
}
}
func requestHandler(ctx *fasthttp.RequestCtx) {
fmt.Fprintf(ctx, "Hello, world!\n\n")
fmt.Fprintf(ctx, "Request method is %q\n", ctx.Method())
fmt.Fprintf(ctx, "RequestURI is %q\n", ctx.RequestURI())
fmt.Fprintf(ctx, "Requested path is %q\n", ctx.Path())
fmt.Fprintf(ctx, "Host is %q\n", ctx.Host())
fmt.Fprintf(ctx, "Query string is %q\n", ctx.QueryArgs())
fmt.Fprintf(ctx, "User-Agent is %q\n", ctx.UserAgent())
fmt.Fprintf(ctx, "Connection has been established at %s\n", ctx.ConnTime())
fmt.Fprintf(ctx, "Request has been started at %s\n", ctx.Time())
fmt.Fprintf(ctx, "Serial request number for the current connection is %d\n", ctx.ConnRequestNum())
fmt.Fprintf(ctx, "Your ip is %q\n\n", ctx.RemoteIP())
fmt.Fprintf(ctx, "Raw request is:\n---CUT---\n%s\n---CUT---", &ctx.Request)
ctx.SetContentType("text/plain; charset=utf8")
// Set arbitrary headers
ctx.Response.Header.Set("X-My-Header", "my-header-value")
// Set cookies
var c fasthttp.Cookie
c.SetKey("cookie-name")
c.SetValue("cookie-value")
ctx.Response.Header.SetCookie(&c)
}
r.Body
->ctx.PostBody()
r.URL.Path
->ctx.Path()
r.URL
->ctx.URI()
r.Method
->ctx.Method()
r.Header
->ctx.Request.Header
r.Header.Get()
->ctx.Request.Header.Peek()
r.Host
->ctx.Host()
r.Form
->ctx.QueryArgs()
+ctx.PostArgs()
r.PostForm
->ctx.PostArgs()
r.FormValue()
->ctx.FormValue()
r.FormFile()
->ctx.FormFile()
r.MultipartForm
->ctx.MultipartForm()
r.RemoteAddr
->ctx.RemoteAddr()
r.RequestURI
->ctx.RequestURI()
r.TLS
->ctx.IsTLS()
r.Cookie()
->ctx.Request.Header.Cookie()
r.Referer()
->ctx.Referer()
r.UserAgent()
->ctx.UserAgent()
w.Header()
->ctx.Response.Header
w.Header().Set()
->ctx.Response.Header.Set()
w.Header().Set("Content-Type")
->ctx.SetContentType()
w.Header().Set("Set-Cookie")
->ctx.Response.Header.SetCookie()
w.Write()
->ctx.Write()
,ctx.SetBody()
,ctx.SetBodyStream()
,ctx.SetBodyStreamWriter()
w.WriteHeader()
->ctx.SetStatusCode()
w.(http.Hijacker).Hijack()
->ctx.Hijack()
http.Error()
->ctx.Error()
http.FileServer()
->fasthttp.FSHandler()
,fasthttp.FS
http.ServeFile()
->fasthttp.ServeFile()
http.Redirect()
->ctx.Redirect()
http.NotFound()
->ctx.NotFound()
http.StripPrefix()
->fasthttp.PathRewriteFunc
fasthttp/router
/user/{user}
named/user/{user}_admin
/user/{user?}
optional/user/{name:[a-zA-Z]{5}}
regex/user/{name?:[a-zA-Z]{5}}
/user/{name:*}
cache all
fasthttp/session
package main
import (
"log"
"time"
"github.com/fasthttp/session/v2"
"github.com/fasthttp/session/v2/providers/redis"
)
var serverSession *session.Session
func init() {
var provider session.Provider
var err error
encoder := session.MSGPEncode
decoder := session.MSGPDecode
provider, err = redis.New(redis.Config{
KeyPrefix: "session",
Addr: "127.0.0.1:6379",
PoolSize: 8,
IdleTimeout: 30 * time.Second,
})
if err != nil {
log.Fatal(err)
}
cfg := session.NewDefaultConfig()
cfg.EncodeFunc = encoder
cfg.DecodeFunc = decoder
serverSession = session.New(cfg)
if err = serverSession.SetProvider(provider); err != nil {
log.Fatal(err)
}
}
// 获取 SessionStore
store, err := serverSession.Get(ctx)
if err != nil {
ctx.Error(err.Error(), fasthttp.StatusInternalServerError)
return
}
defer func() {
if err := serverSession.Save(ctx, store); err != nil {
ctx.Error(err.Error(), fasthttp.StatusInternalServerError)
}
}()
// Set
store.Set("foo", "bar")
// Get
val := store.Get("foo")
if val == nil {
// ...
}
store.Delete(key)
store.Set(bytesKey)
val := store.GetBytes(bytesKey)
store.DeleteBytes(bytesKey)
data := store.GetAll() // Dict
data := store.Ptr() // *Dict
store.Flush()
store.Reset()
// Session ID,默认 sessionid
id := store.GetSessionID()
store.SetSessionID(bytesID)
// Session 有效期
changed := store.HasExpirationChanged()
duration := store.GetExpiration()
err := store.SetExpiration(duration)
savsgio/atreugo
构建与 fasthttp 和 fasthttp/router 之上的 HTTP 框架,做了一些简单的封装工作。
提供了几个示例。
基础用法:
package main
import (
"github.com/savsgio/atreugo/v11"
)
func main() {
config := atreugo.Config{
Addr: "0.0.0.0:8000",
}
server := atreugo.New(config)
server.GET("/", func(ctx *atreugo.RequestCtx) error {
return ctx.TextResponse("Hello World")
})
server.GET("/echo/{path:*}", func(ctx *atreugo.RequestCtx) error {
return ctx.TextResponse("Echo message: " + ctx.UserValue("path").(string))
})
v1 := server.NewGroupPath("/v1")
v1.GET("/", func(ctx *atreugo.RequestCtx) error {
return ctx.TextResponse("Hello V1 Group")
})
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}
#41 Golang: possible resource leak,'defer' is called in the 'for' loop
Golang 2021-04-01类似代码:
for _, f := range files {
fp, err := os.Open(f)
if err != nil {
panic(err)
}
defer fp.Close()
// do something
}
Goland 提示:possible resource leak,'defer' is called in the 'for' loop
defer
是在最后函数退出时执行,但是变量已经经过循环覆盖,可能会导致内存泄漏。
似乎 Golang 的 defer 还是不够聪明。
换种写法:
for _, f := range files {
func () {
fp, err := os.Open(f)
if err != nil {
panic(err)
}
defer fp.Close()
// do something
}()
}
#40 Golang ORM 框架
Golang 2021-03-30- GORM
- ent
- Xorm 转到Gitea
- Bun
- Reform
- GoRose
GitHub 搜索结果
- go-gorm/gorm
The fantastic ORM library for Golang, aims to be developer friendly - ent/ent
An entity framework for Go - geektutu/7days-golang
7 days golang programs from scratch (web framework Gee, distributed cache GeeCache, object relational mapping ORM framework GeeORM, rpc framework GeeRPC etc) 7 天用 Go 动手写/从零实现系列 - gogf/gf
GoFrame is a modular, powerful, high-performance and enterprise-class application development framework of Golang. - sqlc-dev/sqlc
Generate type-safe code from SQL - go-xorm/xorm
Simple and Powerful ORM for Go, support mysql,postgres,tidb,sqlite3,mssql,oracle, Moved to https://gitea.com/xorm/xorm - volatiletech/sqlboiler
Generate a Go ORM tailored to your database schema. - go-pg/pg
Golang ORM with focus on PostgreSQL features and performance - go-gorp/gorp
Go Relational Persistence - an ORM-ish library for Go - xo/xo
Command line tool to generate idiomatic Go code for SQL databases supporting PostgreSQL, MySQL, SQLite, Oracle, and Microsoft SQL Server - upper/db
Data access layer for PostgreSQL, CockroachDB, MySQL, SQLite and MongoDB with ORM-like features. - uptrace/bun
SQL-first Golang ORM - xxjwxc/gormt
database to golang struct - steebchen/prisma-client-go
Prisma Client Go is an auto-generated and fully type-safe database client - xormplus/xorm
xorm 是一个简单而强大的 Go 语言 ORM 库,通过它可以使数据库操作非常简便。本库是基于原版 xorm 的定制增强版本,为 xorm 提供类似 ibatis 的配置文件及动态 SQL 支持,支持 AcitveRecord 操作 - go-reform/reform
A better ORM for Go, based on non-empty interfaces and code generation. - gobuffalo/pop
A Tasty Treat For All Your Database Needs - bobohume/gonet
go 分布式服务器,基于内存 mmo - unionj-cloud/go-doudou
go-doudou(doudou pronounce /dəudəu/)is OpenAPI 3.0 (for REST) spec and Protobuf v3 (for grpc) based lightweight microservice framework. It supports monolith service application as well. - gohouse/gorose
GoRose(go orm), a mini database ORM for golang, which inspired by the famous php framwork laravle's eloquent. It will be friendly for php developer and python or ruby developer. Currently provides six major database drivers: mysql,sqlite3,postgres,oracle,mssql, Clickhouse. - huandu/go-sqlbuilder
A flexible and powerful SQL string builder library plus a zero-config ORM.
#39 Golang 入门项目 httpbin 总结
Golang 2021-03-29httpbin 是我练手的一个非常简单的小项目,功能就是:
1. HTTP POST 请求 (POST /
) 提交一个字符串,服务器返回一个 ID。
1. HTTP GET 请求 (GET /xxxx
),返回 ID 对应的字符串。
#38
Golang compress
包
Golang GoStdLib
2021-03-01
- archive: 打包 (tar, zip)
参考:《Golangarchive
包》 - compress: 压缩 (bzip2, flate, gzip, lzw, zlib)
压缩 & 解压缩
import (
"bytes"
"compress/gzip"
"io"
)
func compressData(data string) ([]byte, error) {
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
_, err := gz.Write([]byte(data))
if err != nil {
return nil, err
}
if err := gz.Close(); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func decompressData(compressedData []byte) (string, error) {
r, err := gzip.NewReader(bytes.NewReader(compressedData))
if err != nil {
return "", err
}
defer r.Close()
decompressedData, err := io.ReadAll(r)
if err != nil {
return "", err
}
return string(decompressedData), nil
}
压缩 & 解压缩 & 列出压缩包中的内容
import (
"archive/zip"
)
// 压缩文件
func compressFile(zipFilename string, filenames ...string) error {
zipFile, err := os.Create(zipFilename)
if err != nil {
return err
}
defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
for _, filename := range filenames {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
fi, err := file.Stat()
if err != nil {
return err
}
fileHeader, err := zip.FileInfoHeader(fi)
if err != nil {
return err
}
// 设置文件名(压缩后的文件名)
fileHeader.Name = filename
// 写入文件头部
writer, err := zipWriter.CreateHeader(fileHeader)
if err != nil {
return err
}
// 复制文件内容到压缩包中
_, err = io.Copy(writer, file)
if err != nil {
return err
}
}
return nil
}
// 解压缩文件
func decompressFile(zipFilename, targetDir string) error {
zipReader, err := zip.OpenReader(zipFilename)
if err != nil {
return err
}
defer zipReader.Close()
for _, file := range zipReader.File {
// 打开压缩包中的文件
fileReader, err := file.Open()
if err != nil {
return err
}
defer fileReader.Close()
// 创建目标文件
extractedFilePath := fmt.Sprintf("%s/%s", targetDir, file.Name)
extractedFile, err := os.Create(extractedFilePath)
if err != nil {
return err
}
defer extractedFile.Close()
// 复制文件内容到目标文件
_, err = io.Copy(extractedFile, fileReader)
if err != nil {
return err
}
}
return nil
}
func listFilesInZip(zipFilename string) ([]string, error) {
files := []string{}
zipReader, err := zip.OpenReader(zipFilename)
if err != nil {
return files, err
}
defer zipReader.Close()
for _, file := range zipReader.File {
files = append(files, file.Name)
}
return files, nil
}
#37
Golang archive
包
Golang GoStdLib
2021-03-01
标准库中的 archive
支持 tar
,zip
两种打包格式。
https://en.wikipedia.org/wiki/Tar_(computing)
最常用的 tar 包:
func createTarArchive(sourceDir, targetFile string) error {
file, err := os.Create(targetFile)
if err != nil {
return err
}
defer file.Close()
tarWriter := tar.NewWriter(file)
defer tarWriter.Close()
return filepath.Walk(sourceDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}
if err := tarWriter.WriteHeader(header); err != nil {
return err
}
if !info.IsDir() {
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(tarWriter, file)
if err != nil {
return err
}
}
return nil
})
}
func extractTarArchive(sourceFile, targetDir string) error {
file, err := os.Open(sourceFile)
if err != nil {
return err
}
defer file.Close()
tarReader := tar.NewReader(file)
for {
header, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
targetPath := filepath.Join(targetDir, header.Name)
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
return err
}
if header.Typeflag == tar.TypeReg {
file, err := os.Create(targetPath)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, tarReader)
if err != nil {
return err
}
}
}
return nil
}