Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
安装:go get -u github.com/wike2019/wike_go@v1.0.9
控制器、依赖注入、中间件、表达式、任务组件,redis缓存,服务发现,限流,熔断等
尽管现在基于 go 语言开发的框架处于一个百家争鸣的时代, 但我还打算做一款go微服务框架,通过详细的文档,简洁的API快速迭代开发,简单易用,功能齐全是我们的初衷
项目地址
pull request 的处理过程对于新特性和 bug 是不一样的。在你发起一个新特性的 pull request 之前,你应该先创建一个带有 [Proposal] 标题的 issue。这个proposal 应当描述这个新特性,以及实现方法。提议将会被审查,有可能会被采纳,也有可能会被拒绝。当一个提议被采纳,将会创建一个实现新特性的 pull request。没有遵循上述指南的 pull request 将会被立即关闭。
为 bug 创建的 Pull requests 不需要创建建议 issue。如果你有解决 bug 的办法,请详细描述你的解决方案。
web客户端功能
基于gin的分组,实现了分组路由和控制器路由
package main
import (
"github.com/wike2019/wike_go/src/Web"
)
type IndexController struct {}
func NewIndexController() *IndexController {
return &IndexController{}
}
//执行函数
func(this *IndexController) Index(ctx *gin.Context) string {
return "this is 首页"
}
//实现接口
func(this *IndexController) Name () string {
return "IndexController"
}
func(this *IndexController) Build(goft *Web.Goft){
//注册路由
goft.Handle("GET","/helloworld",this.Index)
}
func main() {
signalChan := make(chan os.Signal, 1)
app:= Web.New(). //初始化脚手架
Mount("",NewIndexController()). //挂载控制器
go func() {
app.Launch()
}()
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
//关闭工作
<-signalChan
}此时访问地址为 http://127.0.0.1:8180/helloworld
此时访问为 http://127.0.0.1:8180/api/helloworld
package main
import (
"github.com/wike2019/wike_go/src/Web"
)
type IndexController struct {}
func NewIndexController() *IndexController {
return &IndexController{}
}
//执行函数
func(this *IndexController) Index(ctx *gin.Context) string {
return "this is 首页"
}
//实现接口
func(this *IndexController) Name () string {
return "IndexController"
}
func(this *IndexController) Build(goft *Web.Goft){
//注册路由
goft.Handle("GET","/helloworld",this.Index)
}
func main() {
signalChan := make(chan os.Signal, 1)
app:= Web.New(). //初始化脚手架
Mount("/api",NewIndexController()). //挂载控制器
go func() {
app.Launch()
}()
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
//关闭工作
<-signalChan
}敬请期待
修改grpc客户端服务发现问题
添加各种案例
中间件的类型bug
grpc的中间件底层修改
服务发现更新节点可能出现节点数量bug
改版selector算法
第一个可用版本上线
修改核心包名,对外的包全部以大写字母开头
添加grpc功能
添加到代码参考,同时开源项目。不太会发布所以此版本不可用
package main
import (
"github.com/wike2019/wike_go/src/Web"
)
type IndexController struct {}
func NewIndexController() *IndexController {
return &IndexController{}
}
//执行函数
func(this *IndexController) Index(ctx *gin.Context) string {
return "this is 首页"
}
//实现接口
func(this *IndexController) Name () string {
return "IndexController"
}
func(this *IndexController) Build(goft *Web.Goft){
//注册路由
goft.Handle("GET","/",this.Index)
}
func main() {
signalChan := make(chan os.Signal, 1)
app:= Web.New(). //初始化脚手架
Mount("",NewIndexController()). //挂载控制器
go func() {
app.Launch()
}()
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
//关闭工作
<-signalChan
}gin 1.15+
golang 1.12+
支持windows7、mac、Linux
支持go module的方式处理依赖
你所需要的组件 例如redis etcd mysql 等
web开发是基于gin的二次封装,更容易的使用,添加更多实用功能
package main
import (
"github.com/wike2019/wike_go/src/Web"
)
type IndexController struct {}
func NewIndexController() *IndexController {
return &IndexController{}
}
//执行函数
func(this *IndexController) Index(ctx *gin.Context) string {
return "this is 首页"
}
//实现接口
func(this *IndexController) Name () string {
return "IndexController"
}
func(this *IndexController) Build(goft *Web.Goft){
//注册路由
goft.Handle("GET","/",this.Index)
}
func main() {
signalChan := make(chan os.Signal, 1)
app:= Web.New(). //初始化脚手架
Mount("",NewIndexController()). //挂载控制器
go func() {
app.Launch()
}()
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
//关闭工作
<-signalChan
}关于版本
版本
状态
积极支持截止时间
发布或预计发布时间
1.0
Beta 版试用中
积极支持将包含常规迭代周期的 BUG 修复、安全问题修复、功能迭代和功能新增;
wike_go采用 x.y.z 的版本号规则来命名各个版本,如 1.0.3 版本,1 即为 x,2 即为 y,3 即为 z,您可以根据该版本规则来制定您对框架的更新计划。
x 表示一个重大版本,当 Hyperf 的核心进行大量的重构变动时,或当存在大量的破坏性 API 变更时,会作为一个 x 版本发布,x 版本变更通常来说是无法与之前的 x 版本兼容,但也不一定代表完全无法兼容,具体根据对应版本的升级指南来进行甄别。
y 表示一个主要功能迭代版本,当一些公开的 API 进行了破坏性的变更后,包括公开 API 的变更和删除,导致前置版本可能无法兼容的时候,会以 y 版本来进行发布。
z 表示一个完全兼容的修复版本,当对各个组件的已有功能进行 BUG 修复或安全修复时,会选择以一个 z 版本来发布,当一个 BUG 导致了某个功能完全无法使用时,亦可能在 z 版本内修复这个 BUG 时对 API 进行破坏性变更,但由于功能此前已经完全无法使用故此类变更不会以 y 版本来发布,除了 BUG 修复,z 版本也可能会包括一些新增的功能或组件,这些功能和组件均不会影响此前的代码使用。
2022-12-31
2020-12-31
server:
port: 8180
mysql: "root:root@tcp(192.168.3.2:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
redis: "192.168.3.2:6379"
etcd: "192.168.3.2:2379"LoadBalance.SelectByRand 随机选择
LoadBalance.RoundRobin 轮询不加权
LoadBalance.SelectByWeightRand 加权轮询
封装了redis操作,目前只支持string类型,hash,list以后会逐渐完善
案例
封装了redis缓存操作,通过DbGetter来决定缓存什么,使代码高度解耦
可以设置校验策略(内置正则校验策略)
自定义策略需要实现如下接口
内置正则策略
案例
gin封装的上下文对象。支持所有*gin.Context操作
最新api请参考,gin官网 https://github.com/gin-gonic/gin
ctx.Query("lastname") 相当于 ctx.Request.URL.Query().Get("lastname")
ctx.DefaultQuery("firstname", "Guest") 当firstname不存在时返回Guest,一般多用于分页 默认第一页
单文件上传
SaveUploadedFile 将文件移动到指定目标
多文件上传
敬请期待
type CachePolicy interface {
Before(key string ) //之前执行
IfNil(key string,v interface{}) //当空值是操作
SetOperation(opt *RedisStringOperation) //设置处理器 目前支持string
}返回string
返回json
抛出错误
sql相关
根据sql返回数组对象
根据sql返回单个对象
根据sql返回数据添加key 返回数据为 { "result": { "name" : "wike" , "id" : 1 } }
根据sql返回数据将数据转换为map后二次加工后返回
func(this *IndexController) Index(ctx *gin.Context) string {
return "111"
}type Person struct {
ID string `uri:"id" binding:"required,uuid"`
Name string `uri:"name" binding:"required"`
}
func(this *IndexController) Index (ctx *gin.Context) Web.Json {
return &Person{ID:"1111",Name:"wike"}
}//缓存穿透 策略
type CrossPolicy struct {
KeyRegx string //检查key的正则
Expire time.Duration //可以配置查找失败过期时间
opt *RedisStringOperation
}
func NewCrossPolicy(keyRegx string,expire time.Duration) *CrossPolicy {
return &CrossPolicy{KeyRegx: keyRegx,Expire:expire}
}
func (this *CrossPolicy) Before(key string ) {
if !regexp.MustCompile(this.KeyRegx).MatchString(key){
panic("error cache key")
}
}
func(this *CrossPolicy) IfNil(key string,v interface{}) {
this.opt.Set(key,v,WithExpire(this.Expire)).Unwrap()
}
func(this *CrossPolicy) SetOperation(opt *RedisStringOperation){
this.opt=opt
}
func(this *IndexController) Index(ctx *gin.Context) string {
ctx.DefaultQuery("firstname", "Guest")
ctx.Query("lastname")
return "this is 首页"
}func(this *IndexController) Index(ctx *gin.Context) string {
message := ctx.PostForm("message")
nick := ctx.DefaultPostForm("nick", "anonymous")
return "this is 首页"
}func(this *IndexController) Index(ctx *gin.Context) string {
file, _ := ctx.FormFile("file")
log.Println(file.Filename)
dest:="./"+file.Filename
ctx.SaveUploadedFile(file, dst)
return "this is 首页"
}func(this *IndexController) Index(ctx *gin.Context) string {
form, _ := ctx.MultipartForm()
files := form.File["upload[]"]
for _, file := range files {
log.Println(file.Filename)
dest:="./"+file.Filename
ctx.SaveUploadedFile(file, dst)
}
return "this is 首页"
}type Person struct {
Name string `form:"name"`
Address string `form:"address"`
}
func(this *IndexController) Index(ctx *gin.Context) string {
if ctx.ShouldBind(&person) == nil {
log.Println("====== Only Bind By Query String ======")
log.Println(person.Name)
log.Println(person.Address)
}
return "this is 首页"
}
type Person struct {
ID string `uri:"id" binding:"required,uuid"`
Name string `uri:"name" binding:"required"`
}
func(this *IndexController) Index(ctx *gin.Context) string {
if err := ctx.ShouldBindUri(&person); err != nil {
return "参数错误"
}
return "this is 首页"
}
type testHeader struct {
Rate int `header:"Rate"`
Domain string `header:"Domain"`
}
func(this *IndexController) Index(ctx *gin.Context) string {
if err := ctx.ShouldBindHeader(&testHeader); err != nil {
return "heander头错误"
}
return "this is 首页"
}
func(this *IndexController) Index(ctx *gin.Context) string {
cookie, err := ctx.Cookie("gin_cookie")
return cookie
}func(this *IndexController) Index(ctx *gin.Context) string {
ctx.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
return "this is 首页"
}func(this *IndexController) Index(ctx *gin.Context) Web.Json {
return gin.H{"resut":"test"}
}func(this *IndexController) Index (ctx *gin.Context) Web.Json {
u:=&User{}
fmt.Println(this.Db)
Web.Error(fmt.Errorf("抛出一个错误"),"这个是返回的提示消息")
}func(this *IndexController) Index (ctx *gin.Context) Web.Json {
Web.Throw("错误消息",500,ctx)
}func(this *IndexController) Index (ctx *gin.Context) sql.Query {
return sql.SimpleQuery("select * from users where user_id > ?").WithArgs(1)
}func(this *IndexController) Index (ctx *gin.Context) sql.Query {
return sql.SimpleQuery("select * from users where user_id = ?").WithArgs(1).WithFirst()
}func(this *IndexController) Index (ctx *gin.Context) sql.Query {
return sql.SimpleQuery("select * from users where user_id = ?").WithArgs(1).WithFirst().WithKey("result")
}func(this *IndexController) Index (ctx *gin.Context) Web.Json {
//u:=&User{}
ret:= sql.SimpleQuery("select * from users where user_id = ?").WithArgs(1).WithFirst().Get()
m := ret.(map[string]interface{})
m["additive"]="添加一些数据"
return m
}package main
import (
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/wike2019/wike_go/src/util/Crypto"
)
func main() {
crypto := Crypto.New()
origData := []byte("Hello World 11111233") // 待加密的数据
fmt.Println("原文:", string(origData))
decrypted := crypto.AesDecryptCBC(encrypted)
fmt.Println("解密结果:", string(decrypted))
}
package main
import (
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/wike2019/wike_go/src/util/Crypto"
)
func main() {
crypto := Crypto.New()
fmt.Println(crypto.Md5("wike is ok"))
}package main
import (
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/wike2019/wike_go/src/util/Crypto"
)
func main() {
crypto := Crypto.New()
fmt.Println(crypto.Sha256("wike is ok"))
}package main
import (
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/wike2019/wike_go/src/util/Crypto"
)
func main() {
crypto := Crypto.New()
crypto.RSAGenKey(1024,"./public/key")
}package main
import (
"encoding/base64"
"encoding/hex"
"fmt"
"github.com/wike2019/wike_go/src/util/Crypto"
)
func main() {
crypto := Crypto.New()
data,_:=crypto.EncyptogRSA(origData,"./public/key/publicKey.pem")
fmt.Println("加密之后的数据为:",string(data))
data,_=crypto.DecrptogRSA(data,"./public/key/privateKey.pem")
fmt.Println("解密之后的数据为:",string(data))
}敬请期待
OnResponse:执行控制器方法后。可以修改返回值内容
注意点:两个方法必须都有返回值。
type Fairing interface {
OnRequest(*gin.Context) error
OnResponse(result interface{}) (interface{}, error)
}自定义消息规则 vmsg:"验证tag=自定义消息提示"
Validate.New().AddValiDate("NewEmail", func(fl validator.FieldLevel) bool {
return true
})package main
import (
"fmt"
"github.com/wike2019/wike_go/src/util/Archive"
)
func main() {
instance:=Archive.New() //创建对象
instance.TarFile("./用于压缩测试/my.txt","./txt.tar")
instance.UnTarFile("./txt.tar","./用于压缩解压测试")
instance.TarDir("./用于压缩测试","./go.tar")
instance.UnTarDir("./go.tar","./压缩解压测试tmp")
instance.Zip("./用于压缩测试","./压缩解压测试tmp.zip")
instance.UnZip("./压缩解压测试tmp.zip","./压缩解压测试zip")
}type TokenCheck struct {}
func NewTokenCheck() *TokenCheck {
return &TokenCheck{}
}
func(this *TokenCheck) OnRequest(ctx *gin.Context) error{
return nil
}
func(this *TokenCheck) OnResponse(result interface{}) (interface{}, error){
return result,nil
}type AddVersion struct {
}
func NewAddVersion() *AddVersion {
return &AddVersion{}
}
func(this *AddVersion) OnRequest(ctx *gin.Context) error{
return nil
}
func(this *AddVersion) OnResponse(result interface{}) (interface{}, error){
if m,ok:=result.(gin.H);ok{
m["version"]="0.3.0"
return m,nil
}
return result,nil
}func main() {
Web.New().
Attach(NewTokenCheck(),NewAddVersion()).
Mount("v1",NewIndexController()).
Launch()
}type CheckVersion struct {
}
func NewCheckVersion() *CheckVersion {
return &CheckVersion {}
}
func(this *CheckVersion) OnRequest(ctx *gin.Context) error{
if ctx.Query("wike")==""{
Web.Throw("wike requred",503,ctx)
}
return nil
}
func(this *CheckVersion) OnResponse(result interface{}) (interface{}, error){
return result,nil
}type CheckName struct {
}
func NewCheckName() *CheckName {
return &CheckName {}
}
func(this *CheckName ) OnRequest(ctx *gin.Context) error{
if ctx.Query("wike")==""{
Web.Throw("wikerequred",503,ctx)
}
return nil
}
func(this *CheckName ) OnResponse(result interface{}) (interface{}, error){
return result,nil
}func(this *IndexController) Build(goft *Web.Goft){
goft.HandleWithFairing("GET","/index",this.Index, NewCheckName(),NewCheckVersion()).user:=&User{Name:"不合法的名字"}
err:=Validate.New().Validate.Struct(user)
fmt.Println(Validate.New().Msg(user,err))type User struct {
Name string `form:"name" binding:"required,CheckName" json:"name" gorm:"column:user_name" name:"user_name" vmsg:"required=用户名必填,CheckName=我是提示信息"`
}package main
import (
"fmt"
"github.com/wike2019/wike_go/src/util/Compress"
)
func main() {
compress:=Compress.New()
compress.Gzip("./用于压缩测试","./test.gzip")
compress.UnGzip("test.gzip","./用于压缩测试gzip")
data:=compress.Zlib([]byte("key"))
fmt.Println(data)
data,_=compress.UnZlib(data)
fmt.Println(string(data))
}敬请期待
敬请期待
第二点 请装 protoc-go-inject-tag 用于注入tag 很有用
第三点 文件存放位置,所以proto文件统一一个目录,这样好管理
第四点 模型定义集中,我们案例是全部定义到Models.proto
普通调用
服务端流调用
客户端流调用
双向流调用
服务发现调用
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) errorfunc RecoveryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
defer func() {
if e := recover(); e != nil {
debug.PrintStack()
err = status.Errorf(codes.Internal, "Panic err: %v", e)
}
}()
return handler(ctx, req)
}func StreamLoggingInterceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error{
log.Printf("gRPC method: %s, %v", info.FullMethod)
err := handler(srv, stream)
log.Printf("gRPC method: %s", info.FullMethod)
return err
}本教程只供参考
全功能 ORM
关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
Create,Save,Update,Delete,Find 中钩子方法
支持 Preload、Joins 的预加载
事务,嵌套事务,Save Point,Rollback To Saved Point
Context,预编译模式,DryRun 模式
批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
复合主键,索引,约束
Auto Migration
自定义 Logger
灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
每个特性都经过了测试的重重考验
开发者友好
例如:
type User struct {
gorm.Model
Name string
Age sql.NullInt64
Birthday time.Time
Email string
gorm:"type:varchar(100);unique_index"
Role string gorm:"size:255" //设置字段的大小为255个字节
MemberNumber string gorm:"unique;not null"
GORM 倾向于约定,而不是配置。默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间
遵循 GORM 已有的约定,可以减少您的配置和代码量。如果约定不符合您的需求,GORM 允许您自定义配置它们
GORM 定义一个 gorm.Model 结构体,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt
您可以将它嵌入到您的结构体中,以包含这几个字段,详情请参考 嵌入结构体
可导出的字段在使用 GORM 进行 CRUD 时拥有全部的权限,此外,GORM 允许您用标签控制字段级别的权限。这样您就可以让一个字段的权限是只读、只写、只创建、只更新或者被忽略
注意: 使用 GORM Migrator 创建表时,不会创建被忽略的字段
GORM 约定使用 CreatedAt、UpdatedAt 追踪创建/更新时间。如果您定义了这种字段,GORM 在创建、更新时会自动填充 当前时间
要使用不同名称的字段,您可以配置 autoCreateTim、autoUpdateTim 标签
如果您想要保存 UNIX(毫/纳)秒时间戳,而不是 time,您只需简单地将 time.Time 修改为 int 即可
对于匿名字段,GORM 会将其字段包含在父结构体中,例如:
对于正常的结构体字段,你也可以通过标签 embedded 将其嵌入,例如:
并且,您可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀,例如:
声明 model 时,tag 是可选的,GORM 支持以下 tag: tag 名大小写不敏感,但建议使用 camelCase 风格
标签名
说明
column
指定 db 列名
type
列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null、size, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INSTREMENT
size
指定列大小,例如:size:256
primaryKey
指定列为主键
unique
指定列为唯一
创建记录并更新给出的字段。
创建记录并更新未给出的字段。
要有效地插入大量记录,请将一个 slice 传递给 Create 方法。 将切片数据传递给 Create 方法,GORM 将生成一个单一的 SQL 语句来插入所有数据,并回填主键的值,钩子方法也会被调用。
使用 CreateInBatches 创建时,你还可以指定创建的数量,例如:
Upsert 和 Create With Associations 也支持批量插入
说明 初始化GORM时使用CreateBatchSize选项,所有的INSERT在创建record & associations时都会尊重这个选项
GORM允许用户定义的钩子被实现为beforeave, beforereate, AfterSave, AfterCreate。这些钩子方法将在创建记录时被调用,有关生命周期的详细信息请参考钩子
如果你想跳过钩子方法,你可以使用SkipHooks会话模式,例如
GORM支持创建 map[string]interface{}和[]map[string]interface{}{},例如:
注意当创建from map时,钩子不会被调用,关联不会被保存,主键值也不会被填回
ORM允许使用SQL表达式插入数据,有两种方法可以实现这个目标,create from map[string]interface{}或自定义数据类型,例如:
当创建一些带有关联的数据时,如果它的关联值不是零值,那么这些关联将被插入,并且它的hook方法将被调用
你可以用Select跳过保存关联,省略,例如:
你可以为带有default标签的字段定义默认值,例如:
然后,当为零值字段插入数据库时,将使用默认值
注意任何像0,",false这样的零值不会被保存到数据库中,对于那些定义了默认值的字段,你可能想要使用指针类型或Scanner/Valuer来避免这种情况,例如:
注意:你必须为在数据库中有默认值或虚拟/生成值的字段设置默认标记,如果你想在迁移时跳过默认值定义,你可以使用default:(-),例如:
当使用虚拟/生成的值时,您可能需要禁用其创建/更新权限,检查字段级权限
GORM为不同的数据库提供兼容的Upsert支持
在高级查询中还检出firststorinit和firststorcreate
查看原始SQL和SQL Builder以获取更多细节
GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误
如果你想避免ErrRecordNotFound错误,你可以使用Find,比如db.Limit(1).Find(&user)
First, Last方法将根据主键找到第一个/最后一个记录顺序,它只在使用struct查询或提供模型值时有效,如果没有为当前模型定义主键,将根据第一个字段排序,例如:
通过使用内联条件,可以使用主键检索对象。使用字符串时要格外小心,以避免SQL注入,详细信息请参阅安全部分
注意:当使用struct查询时,GORM只会查询非零字段,这意味着如果你的字段的值是0,",false或其他零值,它不会被用于构建查询条件,例如
您可以使用map来构建查询条件,例如:
工作原理类似于Where。
建造NOT条件 工作原理类似于Where。
还可以在高级查询中检查组条件,它可以用于编写复杂的SQL
指定要从数据库检索的字段,默认情况下,选择所有字段
也可以查看智能选择字段
从数据库中检索记录时指定顺序
Limit指定要检索的记录的最大数量Offset指定在开始返回记录之前要跳过的记录的数量
关于如何制作分页器的签出分页
从模型中选择不同的值
Distinct 也可以使用Pluck Count
指定连接条件
你可以在单个SQL中使用连接急切加载关联,例如:
有关详细信息,请参阅预加载(即时加载)
扫描结果到一个结构的工作类似于查找
GORM 允许通过 Select 方法选择特定的字段,如果您在应用程序中经常使用此功能,你也可以定义一个较小的结构体,以实现调用 API 时自动选择特定的字段,例如:
说明QueryFields模式将按所有字段的名称选择当前模型
GORM支持不同类型的锁,例如:
更多细节请参考原始SQL和SQL Builder
子查询可以嵌套在查询中,当使用* GORM . db对象作为参数时,GORM可以生成子查询
GORM允许在方法表的FROM子句中使用子查询,例如:
更容易编写带有组条件的复杂SQL查询
GORM支持sql命名参数。NamedArg或map[string]接口{}{},例如:
查看Raw SQL和SQL Builder了解更多细节
GORM允许扫描结果映射[string]interface{}或[]map[string]interface{},不要忘记指定模型或表,例如:
获取第一个匹配的记录或用给定的条件初始化一个新的实例(仅适用于struct或map条件)
用更多的属性初始化struct如果没有找到记录,这些Attrs将不会被用于构建SQL查询
为struct分配属性,无论是否找到,这些属性都不会用于构建SQL查询,最终数据也不会保存到数据库中
获取第一个匹配的记录或创建一个给定条件的新记录(仅适用于struct, map条件)
创建具有更多属性的结构,如果没有找到记录,这些属性将不会被用于构建SQL查询
为记录分配属性,不管是否找到记录,并将它们保存回数据库
优化器提示用于控制查询优化器选择某个查询执行计划,GORM 通过 gorm.io/hints 提供支持,例如:
索引提示允许向数据库传递索引提示,以防查询规划器搞混。
请参考优化Optimizer Hints/Index/Comment了解更多细节
GORM支持遍历行
批量查询和处理记录
GORM允许hook在查询后被调用,当查询一个记录时,它将被调用,参考hook获取详细信息
从数据库查询单个列并扫描到一个切片,如果要查询多个列,则使用Select和scan代替
作用域允许您指定可以作为方法调用引用的常用查询
查看Scopes详细信息
获取匹配记录计数
Save 会保存所有的字段,即使字段是零值
当使用 Update 更新单个列时,你需要指定条件,否则会返回 ErrMissingWhereClause 错误,查看 Block Global Updates 获取详情。当使用了 Model 方法,且该对象主键有值,该值会被用于构建条件,例如:
Updates 方法支持 struct 和 map[string]interface{} 参数。当使用 struct 更新时,默认情况下,GORM 只会更新非零值的字段
注意 当通过 struct 更新时,GORM 只会更新非零字段。 如果您想确保指定字段被更新,你应该使用
Select更新选定字段,或使用map来完成更新操作
如果您想要在更新时选定、忽略某些字段,您可以使用 Select、Omit
对于更新操作,GORM 支持 BeforeSave、BeforeUpdate、AfterSave、AfterUpdate 钩子,这些方法将在更新记录时被调用,详情请参阅 钩子
如果您尚未通过 Model 指定记录的主键,则 GORM 会执行批量更新
如果在没有任何条件的情况下执行批量更新,默认情况下,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误
对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:
获取受更新影响的行数
GORM 允许使用 SQL 表达式更新列,例如:
并且 GORM 也允许使用 SQL 表达式、自定义数据类型的 Context Valuer 来更新,例如:
使用子查询更新表
如果您想在更新时跳过 Hook 方法且不追踪更新时间,可以使用 UpdateColumn、UpdateColumns,其用法类似于 Update、Updates
GORM 提供了 Changed 方法,它可以被用在 Before Update Hook 里,它会返回字段是否有变更的布尔值
Changed 方法只能与 Update、Updates 方法一起使用,并且它只是检查 Model 对象字段的值与 Update、Updates 的值是否相等,如果值有变更,且字段没有被忽略,则返回 true
若要在 Before 钩子中改变要更新的值,如果它是一个完整的更新,可以使用 Save;否则,应该使用 SetColumn ,例如:
删除一条记录时,删除对象需要指定主键,否则会触发 批量 Delete,例如:
GORM 允许通过内联条件指定主键来检索对象,但只支持整型数值,因为 string 可能导致 SQL 注入。查看 内联条件查询 获取详情
对于删除操作,GORM 支持 BeforeDelete、AfterDelete Hook,在删除记录时会调用这些方法,查看 Hook 获取详情
如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录
如果在没有任何条件的情况下执行批量删除,GORM 不会执行该操作,并返回 ErrMissingWhereClause 错误
对此,你必须加一些条件,或者使用原生 SQL,或者启用 AllowGlobalUpdate 模式,例如:
如果您的模型包含了一个 gorm.DeletedAt 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!
拥有软删除能力的模型调用 Delete 时,记录不会被从数据库中真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过正常的查询方法找到该记录。
如果您不想引入 gorm.Model,您也可以这样启用软删除特性:
您可以使用 Unscoped 找到被软删除的记录
您也可以使用 Unscoped 永久删除匹配的记录
原生查询 SQL 和 Scan
Exec 原生 SQL
注意 GORM 允许缓存预编译 SQL 语句来提高性能,查看 性能 获取详情
GORM 支持 sql.NamedArg、map[string]interface{}{} 或 struct 形式的命名参数,例如:
在不执行的情况下生成 SQL ,可以用于准备或测试生成的 SQL,详情请参考 Session
获取 *sql.Row 结果
获取 *sql.Rows 结果
转到 FindInBatches 获取如何在批量中查询和处理记录的信息, 转到 Group 条件 获取如何构建复杂 SQL 查询的信息
使用 ScanRows 将一行记录扫描至 struct,例如:
GORM 内部使用 SQL builder 生成 SQL。对于每个操作,GORM 都会创建一个 *gorm.Statement 对象,所有的 GORM API 都是在为 statement 添加/修改 Clause,最后,GORM 会根据这些 Clause 生成 SQL
例如,当通过 First 进行查询时,它会在 Statement 中添加以下 Clause
然后 GORM 在 Query callback 中构建最终的查询 SQL,像这样:
生成 SQL:
您可以自定义 Clause 并与 GORM 一起使用,这需要实现 Interface 接口
可以参考 示例
不同的数据库, Clause 可能会生成不同的 SQL,例如:
之所以支持 Clause,是因为 GORM 允许数据库驱动程序通过注册 Clause Builder 来取代默认值,这儿有一个 Limit 的示例
GORM 定义了很多 Clause,其中一些 Clause 提供了你可能会用到的选项
尽管很少会用到它们,但如果你发现 GORM API 与你的预期不符合。这可能可以很好地检查它们,例如:
GORM 提供了 StatementModifier 接口,允许您修改语句,使其符合您的要求,这儿有一个 Hint 示例
gorm:"AUTO_INCREMENT" Address string gorm:"index:addr" // 给 Address 创建一个名字是 addr的索引
IgnoreMe int gorm:"-" //忽略这个字段 }
default
指定列的默认值
precision
指定列的精度
scale
指定列大小
not null
指定列为 NOT NULL
autoIncrement
指定列为自动增长
embedded
嵌套字段
embeddedPrefix
嵌入字段的列名前缀
autoCreateTime
创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
autoUpdateTime
创建/更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli
index
根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情
uniqueIndex
与 index 相同,但创建的是唯一索引
check
创建检查约束,例如 check:age > 13,查看 约束 获取详情
<-
设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限
->
设置字段读的权限,->:false 无读权限
-
忽略该字段,- 无读写权限、