From 329a22f0b1c9a0624941bd2bfce31ad64bd8bd62 Mon Sep 17 00:00:00 2001 From: unclesam <2036253946@qq.com> Date: Tue, 7 Jul 2026 21:21:33 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E3=80=81=E6=97=A5=E5=BF=97=E3=80=81=E5=93=8D=E5=BA=94=E7=AD=89?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E4=B8=AD=E9=97=B4=E4=BB=B6=E4=B8=8E=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/server/server.go | 7 + configs/config.yaml | 8 +- go.mod | 3 + go.sum | 26 ++ internal/conf/conf.go | 1 + internal/conf/conf_redis.go | 7 + internal/db/redis.go | 41 ++ internal/ent/client.go | 182 +++++++- internal/ent/ent.go | 2 + internal/ent/hook/hook.go | 12 + internal/ent/migrate/schema.go | 41 ++ internal/ent/mutation.go | 574 +++++++++++++++++++++++- internal/ent/predicate/predicate.go | 3 + internal/ent/runtime.go | 11 + internal/ent/schema/role.go | 24 + internal/ent/schema/user.go | 5 +- internal/ent/tx.go | 5 +- internal/ent/user.go | 28 +- internal/ent/user/user.go | 35 ++ internal/ent/user/where.go | 24 + internal/ent/user_create.go | 32 ++ internal/ent/user_query.go | 114 ++++- internal/ent/user_update.go | 163 +++++++ internal/middleware/auth.go | 159 +++++++ internal/middleware/logger.go | 83 ++++ internal/middleware/trace_id.go | 15 + internal/server/server.go | 3 + internal/service/redis_ser/redis_jwt.go | 45 ++ internal/utils/res/enter.go | 146 ++++++ internal/utils/validata/enter.go | 48 ++ 30 files changed, 1835 insertions(+), 12 deletions(-) create mode 100644 internal/conf/conf_redis.go create mode 100644 internal/db/redis.go create mode 100644 internal/ent/schema/role.go create mode 100644 internal/middleware/auth.go create mode 100644 internal/middleware/logger.go create mode 100644 internal/middleware/trace_id.go create mode 100644 internal/service/redis_ser/redis_jwt.go create mode 100644 internal/utils/res/enter.go create mode 100644 internal/utils/validata/enter.go diff --git a/cmd/server/server.go b/cmd/server/server.go index 92dfab3..5270139 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -20,6 +20,13 @@ var serverCmd = &cobra.Command{ } defer db.Close() + // 初始化 Redis + err = db.InitRedis(global.Config.Redis.Addr, global.Config.Redis.Password, global.Config.Redis.Db) + if err != nil { + global.Log.Fatal("初始化 Redis 失败", zap.Error(err)) + } + defer db.CloseRedis() + // 初始化 Casbin(会自动连接数据库并在数据库中建立 casbin_rules 表) err = db.InitCasbin() if err != nil { diff --git a/configs/config.yaml b/configs/config.yaml index 5a6f7d2..3f37796 100644 --- a/configs/config.yaml +++ b/configs/config.yaml @@ -28,4 +28,10 @@ jwt: access_secret: "django-insecure-pfgn+96(rq74los_7d#n8#b07+$23vnxy*#y^y=eu9&_1eqk$7" refresh_secret: "3676f2d0e99f294aab75997e16340370" access_expire: 2 # 小时 - refresh_expire: 168 # 小时(7天) \ No newline at end of file + refresh_expire: 168 # 小时(7天) + +# Redis 缓存配置 +redis: + addr: "127.0.0.1:6379" # Redis 连接地址 + password: "wangchan5" # Redis 密码 (没有则为空) + db: 0 # 数据库编号 \ No newline at end of file diff --git a/go.mod b/go.mod index 923c369..374f0c6 100644 --- a/go.mod +++ b/go.mod @@ -29,6 +29,7 @@ require ( github.com/casbin/casbin/v3 v3.8.1 // indirect github.com/casbin/ent-adapter v1.4.0 // indirect github.com/casbin/govaluate v1.10.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/gin-contrib/sse v1.1.0 // indirect @@ -58,6 +59,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect + github.com/redis/go-redis/v9 v9.21.0 // indirect github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spf13/afero v1.15.0 // indirect @@ -69,6 +71,7 @@ require ( github.com/zclconf/go-cty v1.17.0 // indirect github.com/zclconf/go-cty-yaml v1.2.0 // indirect go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/arch v0.22.0 // indirect diff --git a/go.sum b/go.sum index 95984ed..0849e22 100644 --- a/go.sum +++ b/go.sum @@ -34,12 +34,22 @@ github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9my github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0= github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo= +github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= +github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= @@ -110,8 +120,12 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A= github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -121,6 +135,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0 h1:jrYnow5+hy3WRDCBypUFvVKNSPPCdqgSXIE9eJDD8LM= +github.com/olekukonko/ll v0.1.4-0.20260115111900-9e59c2286df0/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew= +github.com/olekukonko/tablewriter v1.1.3 h1:VSHhghXxrP0JHl+0NnKid7WoEmd9/urKRJLysb70nnA= +github.com/olekukonko/tablewriter v1.1.3/go.mod h1:9VU0knjhmMkXjnMKrZ3+L2JhhtsQ/L38BbL3CRNE8tM= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -131,6 +153,8 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E= +github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -180,6 +204,8 @@ github.com/zclconf/go-cty-yaml v1.2.0 h1:GDyL4+e/Qe/S0B7YaecMLbVvAR/Mp21CXMOSiCT github.com/zclconf/go-cty-yaml v1.2.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= diff --git a/internal/conf/conf.go b/internal/conf/conf.go index a7273d2..7a9e7ca 100644 --- a/internal/conf/conf.go +++ b/internal/conf/conf.go @@ -12,6 +12,7 @@ type Config struct { Database DatabaseConfig `mapstructure:"database" yaml:"database"` Logger LoggerConfig `mapstructure:"logger" yaml:"logger"` Jwt JwtConfig `mapstructure:"logger" yaml:"jwt"` + Redis RedisConfig `mapstructure:"redis" yaml:"redis"` } // LoadConfig 读取并解析配置文件 diff --git a/internal/conf/conf_redis.go b/internal/conf/conf_redis.go new file mode 100644 index 0000000..127300c --- /dev/null +++ b/internal/conf/conf_redis.go @@ -0,0 +1,7 @@ +package conf + +type RedisConfig struct { + Addr string `mapstructure:"addr" yaml:"addr"` + Password string `mapstructure:"password" yaml:"password"` + Db int `mapstructure:"db" yaml:"db"` +} diff --git a/internal/db/redis.go b/internal/db/redis.go new file mode 100644 index 0000000..5f9f7e3 --- /dev/null +++ b/internal/db/redis.go @@ -0,0 +1,41 @@ +package db + +import ( + "EnGin/internal/global" + "context" + "fmt" + + "github.com/redis/go-redis/v9" +) + +// Redis 统一导出的 Redis 客户端实例 +var Redis *redis.Client + +// InitRedis 初始化 Redis 连接 +func InitRedis(addr string, password string, dbIndex int) error { + if addr == "" { + return nil + } + rdb := redis.NewClient(&redis.Options{ + Addr: addr, + Password: password, + DB: dbIndex, + }) + // 立即 Ping 一下,验证连接是否成功 + _, err := rdb.Ping(context.Background()).Result() + if err != nil { + return fmt.Errorf("连接 Redis 失败: %w", err) + } + + Redis = rdb + global.Log.Info("Redis 初始化成功") + + return nil +} + +// CloseRedis 关闭 Redis 连接 +func CloseRedis() { + if Redis != nil { + Redis.Close() + } +} diff --git a/internal/ent/client.go b/internal/ent/client.go index 35af32b..00aa987 100644 --- a/internal/ent/client.go +++ b/internal/ent/client.go @@ -11,11 +11,13 @@ import ( "EnGin/internal/ent/migrate" + "EnGin/internal/ent/role" "EnGin/internal/ent/user" "entgo.io/ent" "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" ) // Client is the client that holds all ent builders. @@ -23,6 +25,8 @@ type Client struct { config // Schema is the client for creating, migrating and dropping schema. Schema *migrate.Schema + // Role is the client for interacting with the Role builders. + Role *RoleClient // User is the client for interacting with the User builders. User *UserClient } @@ -36,6 +40,7 @@ func NewClient(opts ...Option) *Client { func (c *Client) init() { c.Schema = migrate.NewSchema(c.driver) + c.Role = NewRoleClient(c.config) c.User = NewUserClient(c.config) } @@ -129,6 +134,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { return &Tx{ ctx: ctx, config: cfg, + Role: NewRoleClient(cfg), User: NewUserClient(cfg), }, nil } @@ -149,6 +155,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) return &Tx{ ctx: ctx, config: cfg, + Role: NewRoleClient(cfg), User: NewUserClient(cfg), }, nil } @@ -156,7 +163,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) // Debug returns a new debug-client. It's used to get verbose logging on specific operations. // // client.Debug(). -// User. +// Role. // Query(). // Count(ctx) func (c *Client) Debug() *Client { @@ -178,18 +185,22 @@ func (c *Client) Close() error { // Use adds the mutation hooks to all the entity clients. // In order to add hooks to a specific client, call: `client.Node.Use(...)`. func (c *Client) Use(hooks ...Hook) { + c.Role.Use(hooks...) c.User.Use(hooks...) } // Intercept adds the query interceptors to all the entity clients. // In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`. func (c *Client) Intercept(interceptors ...Interceptor) { + c.Role.Intercept(interceptors...) c.User.Intercept(interceptors...) } // Mutate implements the ent.Mutator interface. func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { switch m := m.(type) { + case *RoleMutation: + return c.Role.mutate(ctx, m) case *UserMutation: return c.User.mutate(ctx, m) default: @@ -197,6 +208,155 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { } } +// RoleClient is a client for the Role schema. +type RoleClient struct { + config +} + +// NewRoleClient returns a client for the Role from the given config. +func NewRoleClient(c config) *RoleClient { + return &RoleClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `role.Hooks(f(g(h())))`. +func (c *RoleClient) Use(hooks ...Hook) { + c.hooks.Role = append(c.hooks.Role, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `role.Intercept(f(g(h())))`. +func (c *RoleClient) Intercept(interceptors ...Interceptor) { + c.inters.Role = append(c.inters.Role, interceptors...) +} + +// Create returns a builder for creating a Role entity. +func (c *RoleClient) Create() *RoleCreate { + mutation := newRoleMutation(c.config, OpCreate) + return &RoleCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of Role entities. +func (c *RoleClient) CreateBulk(builders ...*RoleCreate) *RoleCreateBulk { + return &RoleCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *RoleClient) MapCreateBulk(slice any, setFunc func(*RoleCreate, int)) *RoleCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &RoleCreateBulk{err: fmt.Errorf("calling to RoleClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*RoleCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &RoleCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for Role. +func (c *RoleClient) Update() *RoleUpdate { + mutation := newRoleMutation(c.config, OpUpdate) + return &RoleUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *RoleClient) UpdateOne(_m *Role) *RoleUpdateOne { + mutation := newRoleMutation(c.config, OpUpdateOne, withRole(_m)) + return &RoleUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *RoleClient) UpdateOneID(id int) *RoleUpdateOne { + mutation := newRoleMutation(c.config, OpUpdateOne, withRoleID(id)) + return &RoleUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for Role. +func (c *RoleClient) Delete() *RoleDelete { + mutation := newRoleMutation(c.config, OpDelete) + return &RoleDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *RoleClient) DeleteOne(_m *Role) *RoleDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *RoleClient) DeleteOneID(id int) *RoleDeleteOne { + builder := c.Delete().Where(role.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &RoleDeleteOne{builder} +} + +// Query returns a query builder for Role. +func (c *RoleClient) Query() *RoleQuery { + return &RoleQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeRole}, + inters: c.Interceptors(), + } +} + +// Get returns a Role entity by its id. +func (c *RoleClient) Get(ctx context.Context, id int) (*Role, error) { + return c.Query().Where(role.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *RoleClient) GetX(ctx context.Context, id int) *Role { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QueryUsers queries the users edge of a Role. +func (c *RoleClient) QueryUsers(_m *Role) *UserQuery { + query := (&UserClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(role.Table, role.FieldID, id), + sqlgraph.To(user.Table, user.FieldID), + sqlgraph.Edge(sqlgraph.M2M, true, role.UsersTable, role.UsersPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *RoleClient) Hooks() []Hook { + return c.hooks.Role +} + +// Interceptors returns the client interceptors. +func (c *RoleClient) Interceptors() []Interceptor { + return c.inters.Role +} + +func (c *RoleClient) mutate(ctx context.Context, m *RoleMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&RoleCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&RoleUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&RoleUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&RoleDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("ent: unknown Role mutation op: %q", m.Op()) + } +} + // UserClient is a client for the User schema. type UserClient struct { config @@ -305,6 +465,22 @@ func (c *UserClient) GetX(ctx context.Context, id int) *User { return obj } +// QueryRoles queries the roles edge of a User. +func (c *UserClient) QueryRoles(_m *User) *RoleQuery { + query := (&RoleClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, id), + sqlgraph.To(role.Table, role.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, user.RolesTable, user.RolesPrimaryKey...), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *UserClient) Hooks() []Hook { return c.hooks.User @@ -333,9 +509,9 @@ func (c *UserClient) mutate(ctx context.Context, m *UserMutation) (Value, error) // hooks and interceptors per client, for fast access. type ( hooks struct { - User []ent.Hook + Role, User []ent.Hook } inters struct { - User []ent.Interceptor + Role, User []ent.Interceptor } ) diff --git a/internal/ent/ent.go b/internal/ent/ent.go index 48d79db..b325d1f 100644 --- a/internal/ent/ent.go +++ b/internal/ent/ent.go @@ -3,6 +3,7 @@ package ent import ( + "EnGin/internal/ent/role" "EnGin/internal/ent/user" "context" "errors" @@ -73,6 +74,7 @@ var ( func checkColumn(t, c string) error { initCheck.Do(func() { columnCheck = sql.NewColumnCheck(map[string]func(string) bool{ + role.Table: role.ValidColumn, user.Table: user.ValidColumn, }) }) diff --git a/internal/ent/hook/hook.go b/internal/ent/hook/hook.go index e5cc7c1..2fb7343 100644 --- a/internal/ent/hook/hook.go +++ b/internal/ent/hook/hook.go @@ -8,6 +8,18 @@ import ( "fmt" ) +// The RoleFunc type is an adapter to allow the use of ordinary +// function as Role mutator. +type RoleFunc func(context.Context, *ent.RoleMutation) (ent.Value, error) + +// Mutate calls f(ctx, m). +func (f RoleFunc) Mutate(ctx context.Context, m ent.Mutation) (ent.Value, error) { + if mv, ok := m.(*ent.RoleMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *ent.RoleMutation", m) +} + // The UserFunc type is an adapter to allow the use of ordinary // function as User mutator. type UserFunc func(context.Context, *ent.UserMutation) (ent.Value, error) diff --git a/internal/ent/migrate/schema.go b/internal/ent/migrate/schema.go index 1062065..b72a2f7 100644 --- a/internal/ent/migrate/schema.go +++ b/internal/ent/migrate/schema.go @@ -8,6 +8,18 @@ import ( ) var ( + // RolesColumns holds the columns for the "roles" table. + RolesColumns = []*schema.Column{ + {Name: "id", Type: field.TypeInt, Increment: true}, + {Name: "code", Type: field.TypeString, Unique: true}, + {Name: "name", Type: field.TypeString}, + } + // RolesTable holds the schema information for the "roles" table. + RolesTable = &schema.Table{ + Name: "roles", + Columns: RolesColumns, + PrimaryKey: []*schema.Column{RolesColumns[0]}, + } // UsersColumns holds the columns for the "users" table. UsersColumns = []*schema.Column{ {Name: "id", Type: field.TypeInt, Increment: true}, @@ -25,11 +37,40 @@ var ( Columns: UsersColumns, PrimaryKey: []*schema.Column{UsersColumns[0]}, } + // UserRolesColumns holds the columns for the "user_roles" table. + UserRolesColumns = []*schema.Column{ + {Name: "user_id", Type: field.TypeInt}, + {Name: "role_id", Type: field.TypeInt}, + } + // UserRolesTable holds the schema information for the "user_roles" table. + UserRolesTable = &schema.Table{ + Name: "user_roles", + Columns: UserRolesColumns, + PrimaryKey: []*schema.Column{UserRolesColumns[0], UserRolesColumns[1]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "user_roles_user_id", + Columns: []*schema.Column{UserRolesColumns[0]}, + RefColumns: []*schema.Column{UsersColumns[0]}, + OnDelete: schema.Cascade, + }, + { + Symbol: "user_roles_role_id", + Columns: []*schema.Column{UserRolesColumns[1]}, + RefColumns: []*schema.Column{RolesColumns[0]}, + OnDelete: schema.Cascade, + }, + }, + } // Tables holds all the tables in the schema. Tables = []*schema.Table{ + RolesTable, UsersTable, + UserRolesTable, } ) func init() { + UserRolesTable.ForeignKeys[0].RefTable = UsersTable + UserRolesTable.ForeignKeys[1].RefTable = RolesTable } diff --git a/internal/ent/mutation.go b/internal/ent/mutation.go index f8ac5c7..5fbdec1 100644 --- a/internal/ent/mutation.go +++ b/internal/ent/mutation.go @@ -4,6 +4,7 @@ package ent import ( "EnGin/internal/ent/predicate" + "EnGin/internal/ent/role" "EnGin/internal/ent/user" "context" "errors" @@ -24,9 +25,483 @@ const ( OpUpdateOne = ent.OpUpdateOne // Node types. + TypeRole = "Role" TypeUser = "User" ) +// RoleMutation represents an operation that mutates the Role nodes in the graph. +type RoleMutation struct { + config + op Op + typ string + id *int + code *string + name *string + clearedFields map[string]struct{} + users map[int]struct{} + removedusers map[int]struct{} + clearedusers bool + done bool + oldValue func(context.Context) (*Role, error) + predicates []predicate.Role +} + +var _ ent.Mutation = (*RoleMutation)(nil) + +// roleOption allows management of the mutation configuration using functional options. +type roleOption func(*RoleMutation) + +// newRoleMutation creates new mutation for the Role entity. +func newRoleMutation(c config, op Op, opts ...roleOption) *RoleMutation { + m := &RoleMutation{ + config: c, + op: op, + typ: TypeRole, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withRoleID sets the ID field of the mutation. +func withRoleID(id int) roleOption { + return func(m *RoleMutation) { + var ( + err error + once sync.Once + value *Role + ) + m.oldValue = func(ctx context.Context) (*Role, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().Role.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withRole sets the old Role of the mutation. +func withRole(node *Role) roleOption { + return func(m *RoleMutation) { + m.oldValue = func(context.Context) (*Role, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m RoleMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m RoleMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("ent: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *RoleMutation) ID() (id int, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *RoleMutation) IDs(ctx context.Context) ([]int, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []int{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().Role.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetCode sets the "code" field. +func (m *RoleMutation) SetCode(s string) { + m.code = &s +} + +// Code returns the value of the "code" field in the mutation. +func (m *RoleMutation) Code() (r string, exists bool) { + v := m.code + if v == nil { + return + } + return *v, true +} + +// OldCode returns the old "code" field's value of the Role entity. +// If the Role object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *RoleMutation) OldCode(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCode is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCode requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCode: %w", err) + } + return oldValue.Code, nil +} + +// ResetCode resets all changes to the "code" field. +func (m *RoleMutation) ResetCode() { + m.code = nil +} + +// SetName sets the "name" field. +func (m *RoleMutation) SetName(s string) { + m.name = &s +} + +// Name returns the value of the "name" field in the mutation. +func (m *RoleMutation) Name() (r string, exists bool) { + v := m.name + if v == nil { + return + } + return *v, true +} + +// OldName returns the old "name" field's value of the Role entity. +// If the Role object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *RoleMutation) OldName(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldName is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldName requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldName: %w", err) + } + return oldValue.Name, nil +} + +// ResetName resets all changes to the "name" field. +func (m *RoleMutation) ResetName() { + m.name = nil +} + +// AddUserIDs adds the "users" edge to the User entity by ids. +func (m *RoleMutation) AddUserIDs(ids ...int) { + if m.users == nil { + m.users = make(map[int]struct{}) + } + for i := range ids { + m.users[ids[i]] = struct{}{} + } +} + +// ClearUsers clears the "users" edge to the User entity. +func (m *RoleMutation) ClearUsers() { + m.clearedusers = true +} + +// UsersCleared reports if the "users" edge to the User entity was cleared. +func (m *RoleMutation) UsersCleared() bool { + return m.clearedusers +} + +// RemoveUserIDs removes the "users" edge to the User entity by IDs. +func (m *RoleMutation) RemoveUserIDs(ids ...int) { + if m.removedusers == nil { + m.removedusers = make(map[int]struct{}) + } + for i := range ids { + delete(m.users, ids[i]) + m.removedusers[ids[i]] = struct{}{} + } +} + +// RemovedUsers returns the removed IDs of the "users" edge to the User entity. +func (m *RoleMutation) RemovedUsersIDs() (ids []int) { + for id := range m.removedusers { + ids = append(ids, id) + } + return +} + +// UsersIDs returns the "users" edge IDs in the mutation. +func (m *RoleMutation) UsersIDs() (ids []int) { + for id := range m.users { + ids = append(ids, id) + } + return +} + +// ResetUsers resets all changes to the "users" edge. +func (m *RoleMutation) ResetUsers() { + m.users = nil + m.clearedusers = false + m.removedusers = nil +} + +// Where appends a list predicates to the RoleMutation builder. +func (m *RoleMutation) Where(ps ...predicate.Role) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the RoleMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *RoleMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.Role, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *RoleMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *RoleMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (Role). +func (m *RoleMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *RoleMutation) Fields() []string { + fields := make([]string, 0, 2) + if m.code != nil { + fields = append(fields, role.FieldCode) + } + if m.name != nil { + fields = append(fields, role.FieldName) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *RoleMutation) Field(name string) (ent.Value, bool) { + switch name { + case role.FieldCode: + return m.Code() + case role.FieldName: + return m.Name() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *RoleMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case role.FieldCode: + return m.OldCode(ctx) + case role.FieldName: + return m.OldName(ctx) + } + return nil, fmt.Errorf("unknown Role field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *RoleMutation) SetField(name string, value ent.Value) error { + switch name { + case role.FieldCode: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCode(v) + return nil + case role.FieldName: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetName(v) + return nil + } + return fmt.Errorf("unknown Role field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *RoleMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *RoleMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *RoleMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown Role numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *RoleMutation) ClearedFields() []string { + return nil +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *RoleMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *RoleMutation) ClearField(name string) error { + return fmt.Errorf("unknown Role nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *RoleMutation) ResetField(name string) error { + switch name { + case role.FieldCode: + m.ResetCode() + return nil + case role.FieldName: + m.ResetName() + return nil + } + return fmt.Errorf("unknown Role field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *RoleMutation) AddedEdges() []string { + edges := make([]string, 0, 1) + if m.users != nil { + edges = append(edges, role.EdgeUsers) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *RoleMutation) AddedIDs(name string) []ent.Value { + switch name { + case role.EdgeUsers: + ids := make([]ent.Value, 0, len(m.users)) + for id := range m.users { + ids = append(ids, id) + } + return ids + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *RoleMutation) RemovedEdges() []string { + edges := make([]string, 0, 1) + if m.removedusers != nil { + edges = append(edges, role.EdgeUsers) + } + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *RoleMutation) RemovedIDs(name string) []ent.Value { + switch name { + case role.EdgeUsers: + ids := make([]ent.Value, 0, len(m.removedusers)) + for id := range m.removedusers { + ids = append(ids, id) + } + return ids + } + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *RoleMutation) ClearedEdges() []string { + edges := make([]string, 0, 1) + if m.clearedusers { + edges = append(edges, role.EdgeUsers) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *RoleMutation) EdgeCleared(name string) bool { + switch name { + case role.EdgeUsers: + return m.clearedusers + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *RoleMutation) ClearEdge(name string) error { + switch name { + } + return fmt.Errorf("unknown Role unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *RoleMutation) ResetEdge(name string) error { + switch name { + case role.EdgeUsers: + m.ResetUsers() + return nil + } + return fmt.Errorf("unknown Role edge %s", name) +} + // UserMutation represents an operation that mutates the User nodes in the graph. type UserMutation struct { config @@ -41,6 +516,9 @@ type UserMutation struct { ip *string addr *string clearedFields map[string]struct{} + roles map[int]struct{} + removedroles map[int]struct{} + clearedroles bool done bool oldValue func(context.Context) (*User, error) predicates []predicate.User @@ -435,6 +913,60 @@ func (m *UserMutation) ResetAddr() { delete(m.clearedFields, user.FieldAddr) } +// AddRoleIDs adds the "roles" edge to the Role entity by ids. +func (m *UserMutation) AddRoleIDs(ids ...int) { + if m.roles == nil { + m.roles = make(map[int]struct{}) + } + for i := range ids { + m.roles[ids[i]] = struct{}{} + } +} + +// ClearRoles clears the "roles" edge to the Role entity. +func (m *UserMutation) ClearRoles() { + m.clearedroles = true +} + +// RolesCleared reports if the "roles" edge to the Role entity was cleared. +func (m *UserMutation) RolesCleared() bool { + return m.clearedroles +} + +// RemoveRoleIDs removes the "roles" edge to the Role entity by IDs. +func (m *UserMutation) RemoveRoleIDs(ids ...int) { + if m.removedroles == nil { + m.removedroles = make(map[int]struct{}) + } + for i := range ids { + delete(m.roles, ids[i]) + m.removedroles[ids[i]] = struct{}{} + } +} + +// RemovedRoles returns the removed IDs of the "roles" edge to the Role entity. +func (m *UserMutation) RemovedRolesIDs() (ids []int) { + for id := range m.removedroles { + ids = append(ids, id) + } + return +} + +// RolesIDs returns the "roles" edge IDs in the mutation. +func (m *UserMutation) RolesIDs() (ids []int) { + for id := range m.roles { + ids = append(ids, id) + } + return +} + +// ResetRoles resets all changes to the "roles" edge. +func (m *UserMutation) ResetRoles() { + m.roles = nil + m.clearedroles = false + m.removedroles = nil +} + // Where appends a list predicates to the UserMutation builder. func (m *UserMutation) Where(ps ...predicate.User) { m.predicates = append(m.predicates, ps...) @@ -691,48 +1223,84 @@ func (m *UserMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *UserMutation) AddedEdges() []string { - edges := make([]string, 0, 0) + edges := make([]string, 0, 1) + if m.roles != nil { + edges = append(edges, user.EdgeRoles) + } return edges } // AddedIDs returns all IDs (to other nodes) that were added for the given edge // name in this mutation. func (m *UserMutation) AddedIDs(name string) []ent.Value { + switch name { + case user.EdgeRoles: + ids := make([]ent.Value, 0, len(m.roles)) + for id := range m.roles { + ids = append(ids, id) + } + return ids + } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *UserMutation) RemovedEdges() []string { - edges := make([]string, 0, 0) + edges := make([]string, 0, 1) + if m.removedroles != nil { + edges = append(edges, user.EdgeRoles) + } return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *UserMutation) RemovedIDs(name string) []ent.Value { + switch name { + case user.EdgeRoles: + ids := make([]ent.Value, 0, len(m.removedroles)) + for id := range m.removedroles { + ids = append(ids, id) + } + return ids + } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *UserMutation) ClearedEdges() []string { - edges := make([]string, 0, 0) + edges := make([]string, 0, 1) + if m.clearedroles { + edges = append(edges, user.EdgeRoles) + } return edges } // EdgeCleared returns a boolean which indicates if the edge with the given name // was cleared in this mutation. func (m *UserMutation) EdgeCleared(name string) bool { + switch name { + case user.EdgeRoles: + return m.clearedroles + } return false } // ClearEdge clears the value of the edge with the given name. It returns an error // if that edge is not defined in the schema. func (m *UserMutation) ClearEdge(name string) error { + switch name { + } return fmt.Errorf("unknown User unique edge %s", name) } // ResetEdge resets all changes to the edge with the given name in this mutation. // It returns an error if the edge is not defined in the schema. func (m *UserMutation) ResetEdge(name string) error { + switch name { + case user.EdgeRoles: + m.ResetRoles() + return nil + } return fmt.Errorf("unknown User edge %s", name) } diff --git a/internal/ent/predicate/predicate.go b/internal/ent/predicate/predicate.go index af21dfe..c563a27 100644 --- a/internal/ent/predicate/predicate.go +++ b/internal/ent/predicate/predicate.go @@ -6,5 +6,8 @@ import ( "entgo.io/ent/dialect/sql" ) +// Role is the predicate function for role builders. +type Role func(*sql.Selector) + // User is the predicate function for user builders. type User func(*sql.Selector) diff --git a/internal/ent/runtime.go b/internal/ent/runtime.go index 65f7886..9fe03cd 100644 --- a/internal/ent/runtime.go +++ b/internal/ent/runtime.go @@ -3,6 +3,7 @@ package ent import ( + "EnGin/internal/ent/role" "EnGin/internal/ent/schema" "EnGin/internal/ent/user" "time" @@ -12,6 +13,16 @@ import ( // (default values, validators, hooks and policies) and stitches it // to their package variables. func init() { + roleFields := schema.Role{}.Fields() + _ = roleFields + // roleDescCode is the schema descriptor for code field. + roleDescCode := roleFields[0].Descriptor() + // role.CodeValidator is a validator for the "code" field. It is called by the builders before save. + role.CodeValidator = roleDescCode.Validators[0].(func(string) error) + // roleDescName is the schema descriptor for name field. + roleDescName := roleFields[1].Descriptor() + // role.NameValidator is a validator for the "name" field. It is called by the builders before save. + role.NameValidator = roleDescName.Validators[0].(func(string) error) userMixin := schema.User{}.Mixin() userMixinFields0 := userMixin[0].Fields() _ = userMixinFields0 diff --git a/internal/ent/schema/role.go b/internal/ent/schema/role.go new file mode 100644 index 0000000..d596b7a --- /dev/null +++ b/internal/ent/schema/role.go @@ -0,0 +1,24 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" +) + +type Role struct { + ent.Schema +} + +func (Role) Fields() []ent.Field { + return []ent.Field{ + field.String("code").Unique().NotEmpty(), + field.String("name").NotEmpty(), + } +} + +func (Role) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("users", User.Type).Ref("roles"), + } +} diff --git a/internal/ent/schema/user.go b/internal/ent/schema/user.go index 289e1aa..b16b22b 100644 --- a/internal/ent/schema/user.go +++ b/internal/ent/schema/user.go @@ -4,6 +4,7 @@ import ( "regexp" "entgo.io/ent" + "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" ) @@ -40,5 +41,7 @@ func (User) Fields() []ent.Field { // Edges of the User. func (User) Edges() []ent.Edge { - return nil + return []ent.Edge{ + edge.To("roles", Role.Type), + } } diff --git a/internal/ent/tx.go b/internal/ent/tx.go index 8bbe7fa..8487949 100644 --- a/internal/ent/tx.go +++ b/internal/ent/tx.go @@ -12,6 +12,8 @@ import ( // Tx is a transactional client that is created by calling Client.Tx(). type Tx struct { config + // Role is the client for interacting with the Role builders. + Role *RoleClient // User is the client for interacting with the User builders. User *UserClient @@ -145,6 +147,7 @@ func (tx *Tx) Client() *Client { } func (tx *Tx) init() { + tx.Role = NewRoleClient(tx.config) tx.User = NewUserClient(tx.config) } @@ -155,7 +158,7 @@ func (tx *Tx) init() { // of them in order to commit or rollback the transaction. // // If a closed transaction is embedded in one of the generated entities, and the entity -// applies a query, for example: User.QueryXXX(), the query will be executed +// applies a query, for example: Role.QueryXXX(), the query will be executed // through the driver which created this transaction. // // Note that txDriver is not goroutine safe. diff --git a/internal/ent/user.go b/internal/ent/user.go index c840bbb..11b5791 100644 --- a/internal/ent/user.go +++ b/internal/ent/user.go @@ -30,10 +30,31 @@ type User struct { // IP holds the value of the "ip" field. IP string `json:"ip,omitempty"` // Addr holds the value of the "addr" field. - Addr string `json:"addr,omitempty"` + Addr string `json:"addr,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the UserQuery when eager-loading is set. + Edges UserEdges `json:"edges"` selectValues sql.SelectValues } +// UserEdges holds the relations/edges for other nodes in the graph. +type UserEdges struct { + // Roles holds the value of the roles edge. + Roles []*Role `json:"roles,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [1]bool +} + +// RolesOrErr returns the Roles value or an error if the edge +// was not loaded in eager-loading. +func (e UserEdges) RolesOrErr() ([]*Role, error) { + if e.loadedTypes[0] { + return e.Roles, nil + } + return nil, &NotLoadedError{edge: "roles"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*User) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -121,6 +142,11 @@ func (_m *User) Value(name string) (ent.Value, error) { return _m.selectValues.Get(name) } +// QueryRoles queries the "roles" edge of the User entity. +func (_m *User) QueryRoles() *RoleQuery { + return NewUserClient(_m.config).QueryRoles(_m) +} + // Update returns a builder for updating this User. // Note that you need to call User.Unwrap() before calling this method if this User // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/internal/ent/user/user.go b/internal/ent/user/user.go index e204745..6cca783 100644 --- a/internal/ent/user/user.go +++ b/internal/ent/user/user.go @@ -6,6 +6,7 @@ import ( "time" "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" ) const ( @@ -27,8 +28,15 @@ const ( FieldIP = "ip" // FieldAddr holds the string denoting the addr field in the database. FieldAddr = "addr" + // EdgeRoles holds the string denoting the roles edge name in mutations. + EdgeRoles = "roles" // Table holds the table name of the user in the database. Table = "users" + // RolesTable is the table that holds the roles relation/edge. The primary key declared below. + RolesTable = "user_roles" + // RolesInverseTable is the table name for the Role entity. + // It exists in this package in order to avoid circular dependency with the "role" package. + RolesInverseTable = "roles" ) // Columns holds all SQL columns for user fields. @@ -43,6 +51,12 @@ var Columns = []string{ FieldAddr, } +var ( + // RolesPrimaryKey and RolesColumn2 are the table columns denoting the + // primary key for the roles relation (M2M). + RolesPrimaryKey = []string{"user_id", "role_id"} +) + // ValidColumn reports if the column name is valid (part of the table columns). func ValidColumn(column string) bool { for i := range Columns { @@ -112,3 +126,24 @@ func ByIP(opts ...sql.OrderTermOption) OrderOption { func ByAddr(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldAddr, opts...).ToFunc() } + +// ByRolesCount orders the results by roles count. +func ByRolesCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newRolesStep(), opts...) + } +} + +// ByRoles orders the results by roles terms. +func ByRoles(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newRolesStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} +func newRolesStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(RolesInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, RolesTable, RolesPrimaryKey...), + ) +} diff --git a/internal/ent/user/where.go b/internal/ent/user/where.go index 740ac2b..b0b0813 100644 --- a/internal/ent/user/where.go +++ b/internal/ent/user/where.go @@ -7,6 +7,7 @@ import ( "time" "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" ) // ID filters vertices based on their ID field. @@ -524,6 +525,29 @@ func AddrContainsFold(v string) predicate.User { return predicate.User(sql.FieldContainsFold(FieldAddr, v)) } +// HasRoles applies the HasEdge predicate on the "roles" edge. +func HasRoles() predicate.User { + return predicate.User(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, RolesTable, RolesPrimaryKey...), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasRolesWith applies the HasEdge predicate on the "roles" edge with a given conditions (other predicates). +func HasRolesWith(preds ...predicate.Role) predicate.User { + return predicate.User(func(s *sql.Selector) { + step := newRolesStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.User) predicate.User { return predicate.User(sql.AndPredicates(predicates...)) diff --git a/internal/ent/user_create.go b/internal/ent/user_create.go index d80114c..4a35442 100644 --- a/internal/ent/user_create.go +++ b/internal/ent/user_create.go @@ -3,6 +3,7 @@ package ent import ( + "EnGin/internal/ent/role" "EnGin/internal/ent/user" "context" "errors" @@ -102,6 +103,21 @@ func (_c *UserCreate) SetNillableAddr(v *string) *UserCreate { return _c } +// AddRoleIDs adds the "roles" edge to the Role entity by IDs. +func (_c *UserCreate) AddRoleIDs(ids ...int) *UserCreate { + _c.mutation.AddRoleIDs(ids...) + return _c +} + +// AddRoles adds the "roles" edges to the Role entity. +func (_c *UserCreate) AddRoles(v ...*Role) *UserCreate { + ids := make([]int, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddRoleIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (_c *UserCreate) Mutation() *UserMutation { return _c.mutation @@ -235,6 +251,22 @@ func (_c *UserCreate) createSpec() (*User, *sqlgraph.CreateSpec) { _spec.SetField(user.FieldAddr, field.TypeString, value) _node.Addr = value } + if nodes := _c.mutation.RolesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: user.RolesTable, + Columns: user.RolesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(role.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/internal/ent/user_query.go b/internal/ent/user_query.go index 4e83cbf..d86ea9c 100644 --- a/internal/ent/user_query.go +++ b/internal/ent/user_query.go @@ -4,8 +4,10 @@ package ent import ( "EnGin/internal/ent/predicate" + "EnGin/internal/ent/role" "EnGin/internal/ent/user" "context" + "database/sql/driver" "fmt" "math" @@ -22,6 +24,7 @@ type UserQuery struct { order []user.OrderOption inters []Interceptor predicates []predicate.User + withRoles *RoleQuery // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -58,6 +61,28 @@ func (_q *UserQuery) Order(o ...user.OrderOption) *UserQuery { return _q } +// QueryRoles chains the current query on the "roles" edge. +func (_q *UserQuery) QueryRoles() *RoleQuery { + query := (&RoleClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(user.Table, user.FieldID, selector), + sqlgraph.To(role.Table, role.FieldID), + sqlgraph.Edge(sqlgraph.M2M, false, user.RolesTable, user.RolesPrimaryKey...), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first User entity from the query. // Returns a *NotFoundError when no User was found. func (_q *UserQuery) First(ctx context.Context) (*User, error) { @@ -250,12 +275,24 @@ func (_q *UserQuery) Clone() *UserQuery { order: append([]user.OrderOption{}, _q.order...), inters: append([]Interceptor{}, _q.inters...), predicates: append([]predicate.User{}, _q.predicates...), + withRoles: _q.withRoles.Clone(), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, } } +// WithRoles tells the query-builder to eager-load the nodes that are connected to +// the "roles" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *UserQuery) WithRoles(opts ...func(*RoleQuery)) *UserQuery { + query := (&RoleClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withRoles = query + return _q +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -332,8 +369,11 @@ func (_q *UserQuery) prepareQuery(ctx context.Context) error { func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, error) { var ( - nodes = []*User{} - _spec = _q.querySpec() + nodes = []*User{} + _spec = _q.querySpec() + loadedTypes = [1]bool{ + _q.withRoles != nil, + } ) _spec.ScanValues = func(columns []string) ([]any, error) { return (*User).scanValues(nil, columns) @@ -341,6 +381,7 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e _spec.Assign = func(columns []string, values []any) error { node := &User{config: _q.config} nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes return node.assignValues(columns, values) } for i := range hooks { @@ -352,9 +393,78 @@ func (_q *UserQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*User, e if len(nodes) == 0 { return nodes, nil } + if query := _q.withRoles; query != nil { + if err := _q.loadRoles(ctx, query, nodes, + func(n *User) { n.Edges.Roles = []*Role{} }, + func(n *User, e *Role) { n.Edges.Roles = append(n.Edges.Roles, e) }); err != nil { + return nil, err + } + } return nodes, nil } +func (_q *UserQuery) loadRoles(ctx context.Context, query *RoleQuery, nodes []*User, init func(*User), assign func(*User, *Role)) error { + edgeIDs := make([]driver.Value, len(nodes)) + byID := make(map[int]*User) + nids := make(map[int]map[*User]struct{}) + for i, node := range nodes { + edgeIDs[i] = node.ID + byID[node.ID] = node + if init != nil { + init(node) + } + } + query.Where(func(s *sql.Selector) { + joinT := sql.Table(user.RolesTable) + s.Join(joinT).On(s.C(role.FieldID), joinT.C(user.RolesPrimaryKey[1])) + s.Where(sql.InValues(joinT.C(user.RolesPrimaryKey[0]), edgeIDs...)) + columns := s.SelectedColumns() + s.Select(joinT.C(user.RolesPrimaryKey[0])) + s.AppendSelect(columns...) + s.SetDistinct(false) + }) + if err := query.prepareQuery(ctx); err != nil { + return err + } + qr := QuerierFunc(func(ctx context.Context, q Query) (Value, error) { + return query.sqlAll(ctx, func(_ context.Context, spec *sqlgraph.QuerySpec) { + assign := spec.Assign + values := spec.ScanValues + spec.ScanValues = func(columns []string) ([]any, error) { + values, err := values(columns[1:]) + if err != nil { + return nil, err + } + return append([]any{new(sql.NullInt64)}, values...), nil + } + spec.Assign = func(columns []string, values []any) error { + outValue := int(values[0].(*sql.NullInt64).Int64) + inValue := int(values[1].(*sql.NullInt64).Int64) + if nids[inValue] == nil { + nids[inValue] = map[*User]struct{}{byID[outValue]: {}} + return assign(columns[1:], values[1:]) + } + nids[inValue][byID[outValue]] = struct{}{} + return nil + } + }) + }) + neighbors, err := withInterceptors[[]*Role](ctx, query, qr, query.inters) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nids[n.ID] + if !ok { + return fmt.Errorf(`unexpected "roles" node returned %v`, n.ID) + } + for kn := range nodes { + assign(kn, n) + } + } + return nil +} + func (_q *UserQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() _spec.Node.Columns = _q.ctx.Fields diff --git a/internal/ent/user_update.go b/internal/ent/user_update.go index 8cc1aae..56b83f7 100644 --- a/internal/ent/user_update.go +++ b/internal/ent/user_update.go @@ -4,6 +4,7 @@ package ent import ( "EnGin/internal/ent/predicate" + "EnGin/internal/ent/role" "EnGin/internal/ent/user" "context" "errors" @@ -122,11 +123,47 @@ func (_u *UserUpdate) ClearAddr() *UserUpdate { return _u } +// AddRoleIDs adds the "roles" edge to the Role entity by IDs. +func (_u *UserUpdate) AddRoleIDs(ids ...int) *UserUpdate { + _u.mutation.AddRoleIDs(ids...) + return _u +} + +// AddRoles adds the "roles" edges to the Role entity. +func (_u *UserUpdate) AddRoles(v ...*Role) *UserUpdate { + ids := make([]int, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddRoleIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (_u *UserUpdate) Mutation() *UserMutation { return _u.mutation } +// ClearRoles clears all "roles" edges to the Role entity. +func (_u *UserUpdate) ClearRoles() *UserUpdate { + _u.mutation.ClearRoles() + return _u +} + +// RemoveRoleIDs removes the "roles" edge to Role entities by IDs. +func (_u *UserUpdate) RemoveRoleIDs(ids ...int) *UserUpdate { + _u.mutation.RemoveRoleIDs(ids...) + return _u +} + +// RemoveRoles removes "roles" edges to Role entities. +func (_u *UserUpdate) RemoveRoles(v ...*Role) *UserUpdate { + ids := make([]int, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveRoleIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (_u *UserUpdate) Save(ctx context.Context) (int, error) { _u.defaults() @@ -227,6 +264,51 @@ func (_u *UserUpdate) sqlSave(ctx context.Context) (_node int, err error) { if _u.mutation.AddrCleared() { _spec.ClearField(user.FieldAddr, field.TypeString) } + if _u.mutation.RolesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: user.RolesTable, + Columns: user.RolesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(role.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedRolesIDs(); len(nodes) > 0 && !_u.mutation.RolesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: user.RolesTable, + Columns: user.RolesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(role.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RolesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: user.RolesTable, + Columns: user.RolesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(role.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{user.Label} @@ -341,11 +423,47 @@ func (_u *UserUpdateOne) ClearAddr() *UserUpdateOne { return _u } +// AddRoleIDs adds the "roles" edge to the Role entity by IDs. +func (_u *UserUpdateOne) AddRoleIDs(ids ...int) *UserUpdateOne { + _u.mutation.AddRoleIDs(ids...) + return _u +} + +// AddRoles adds the "roles" edges to the Role entity. +func (_u *UserUpdateOne) AddRoles(v ...*Role) *UserUpdateOne { + ids := make([]int, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddRoleIDs(ids...) +} + // Mutation returns the UserMutation object of the builder. func (_u *UserUpdateOne) Mutation() *UserMutation { return _u.mutation } +// ClearRoles clears all "roles" edges to the Role entity. +func (_u *UserUpdateOne) ClearRoles() *UserUpdateOne { + _u.mutation.ClearRoles() + return _u +} + +// RemoveRoleIDs removes the "roles" edge to Role entities by IDs. +func (_u *UserUpdateOne) RemoveRoleIDs(ids ...int) *UserUpdateOne { + _u.mutation.RemoveRoleIDs(ids...) + return _u +} + +// RemoveRoles removes "roles" edges to Role entities. +func (_u *UserUpdateOne) RemoveRoles(v ...*Role) *UserUpdateOne { + ids := make([]int, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveRoleIDs(ids...) +} + // Where appends a list predicates to the UserUpdate builder. func (_u *UserUpdateOne) Where(ps ...predicate.User) *UserUpdateOne { _u.mutation.Where(ps...) @@ -476,6 +594,51 @@ func (_u *UserUpdateOne) sqlSave(ctx context.Context) (_node *User, err error) { if _u.mutation.AddrCleared() { _spec.ClearField(user.FieldAddr, field.TypeString) } + if _u.mutation.RolesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: user.RolesTable, + Columns: user.RolesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(role.FieldID, field.TypeInt), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedRolesIDs(); len(nodes) > 0 && !_u.mutation.RolesCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: user.RolesTable, + Columns: user.RolesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(role.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RolesIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2M, + Inverse: false, + Table: user.RolesTable, + Columns: user.RolesPrimaryKey, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(role.FieldID, field.TypeInt), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &User{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go new file mode 100644 index 0000000..8888484 --- /dev/null +++ b/internal/middleware/auth.go @@ -0,0 +1,159 @@ +package middleware + +import ( + "EnGin/internal/db" + "EnGin/internal/ent/user" + "EnGin/internal/global" + "EnGin/internal/service/redis_ser" + "EnGin/internal/utils/jwts" + "EnGin/internal/utils/res" + "fmt" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +func Auth(c *gin.Context) { + accessToken := c.GetHeader("access_token") + claims, err := jwts.CheckAccessToken(accessToken) + if err != nil { + res.Unauthorized("认证失败", c) + c.Abort() + return + } + + if redis_ser.HasLogout(accessToken) { + res.FailWithMsg("当前登录已经注销", c) + c.Abort() + return + } + + c.Set("claims", claims) + c.Next() +} + +func AuthAdmin(c *gin.Context) { + accessToken := c.GetHeader("access_token") + if accessToken == "" { + res.Unauthorized("未携带 access_token", c) + c.Abort() + return + } + + claims, err := jwts.CheckAccessToken(accessToken) + if err != nil { + res.Unauthorized("认证失败或 Token 已过期", c) + c.Abort() + return + } + + // 💡 预加载用户的关联角色列表 (WithRoles) + ctx := c.Request.Context() + u, err := db.Client.User.Query(). + Where(user.ID(int(claims.UserID))). + WithRoles(). // 预加载关系 + Only(ctx) + + if err != nil { + res.Forbidden("用户不存在", c) + c.Abort() + return + } + + // 💡 遍历用户的角色列表,检查是否有 "admin" 角色代码 + isAdmin := false + for _, role := range u.Edges.Roles { + if role.Code == "admin" { + isAdmin = true + break + } + } + + if !isAdmin { + res.Forbidden("需要管理员权限", c) + c.Abort() + return + } + + c.Set("claims", claims) + c.Next() +} + +func GetAuth(c *gin.Context) (cl *jwts.MyClaims) { + cl = new(jwts.MyClaims) + _claims, ok := c.Get("claims") + if !ok { + return nil + } + cl, ok = _claims.(*jwts.MyClaims) + return +} + +func JwtRefresh(c *gin.Context) { + token := c.Request.Header.Get("refresh_token") + if token == "" { + res.BadRequest("未携带refresh_token", c) + c.Abort() + return + } + + // 解析token + claims, err := jwts.CheckRefreshToken(token) + if err != nil { + res.FailWithMsg("refresh_token 无效", c) + c.Abort() + return + } + + // 检查redis + if redis_ser.HasLogout(token) { + res.FailWithMsg("当前登录已经注销", c) + c.Abort() + return + } + + c.Set("refresh_claims", claims) + c.Next() +} + +func CasbinMiddleware(c *gin.Context) { + // 获取用户信息 + claims := GetAuth(c) + if claims == nil { + res.Forbidden("权限不足", c) + c.Abort() + return + } + + // 获取请求路径方法 + obj := c.Request.URL.Path + act := c.Request.Method + + // 使用 user_ID 作为主体进行权限检查 + sub := fmt.Sprintf("user_%d", claims.UserID) + + // 查看用户的角色 + roles, _ := db.GlobalEnforcer.GetRolesForUser(sub) + + // 检查权限 + success, err := db.GlobalEnforcer.Enforce(sub, obj, act) + if err != nil { + global.Log.Error("权限检查出错", zap.Error(err)) + res.InternalError("权限检查出错", c) + c.Abort() + return + } + + if !success { + global.Log.Warn("权限不足", + zap.String("sub", sub), + zap.String("obj", obj), + zap.String("act", act), + zap.Strings("roles", roles)) + res.Forbidden("权限不足", c) + c.Abort() + return + } + + c.Next() +} diff --git a/internal/middleware/logger.go b/internal/middleware/logger.go new file mode 100644 index 0000000..8b90224 --- /dev/null +++ b/internal/middleware/logger.go @@ -0,0 +1,83 @@ +package middleware + +import ( + "EnGin/internal/global" + "fmt" + "time" + + "github.com/gin-gonic/gin" +) + +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + path := c.Request.URL.Path + raw := c.Request.URL.RawQuery + + c.Next() + + latency := time.Since(start) + clientIP := c.ClientIP() + method := c.Request.Method + statusCode := c.Writer.Status() + traceID := c.GetString("trace_id") + + if raw != "" { + path = path + "?" + raw + } + + // 状态码颜色 + statusColor := getStatusColor(statusCode) + methodColor := getMethodColor(method) + resetColor := "\033[0m" + + logMsg := fmt.Sprintf("%s %3d %s| %13v | %10s | %s%-5s%s %s | trace_id:%s", + statusColor, statusCode, resetColor, + latency, + clientIP, + methodColor, method, resetColor, + path, + traceID, + ) + + if statusCode >= 500 { + global.Log.Error(logMsg) + } else { + global.Log.Info(logMsg) + } + } +} + +func getStatusColor(code int) string { + switch { + case code >= 200 && code < 300: + return "\033[97;42m" // 绿底白字 + case code >= 300 && code < 400: + return "\033[90;47m" // 白底黑字 + case code >= 400 && code < 500: + return "\033[97;43m" // 黄底白字 + default: + return "\033[97;41m" // 红底白字 + } +} + +func getMethodColor(method string) string { + switch method { + case "GET": + return "\033[97;44m" // 蓝底白字 + case "POST": + return "\033[97;46m" // 青底白字 + case "PUT": + return "\033[97;43m" // 黄底白字 + case "DELETE": + return "\033[97;41m" // 红底白字 + case "PATCH": + return "\033[97;42m" // 绿底白字 + case "HEAD": + return "\033[97;45m" // 紫底白字 + case "OPTIONS": + return "\033[90;47m" // 白底黑字 + default: + return "\033[0m" + } +} diff --git a/internal/middleware/trace_id.go b/internal/middleware/trace_id.go new file mode 100644 index 0000000..3f32a6d --- /dev/null +++ b/internal/middleware/trace_id.go @@ -0,0 +1,15 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +func TraceID() gin.HandlerFunc { + return func(c *gin.Context) { + requestID := uuid.New().String() + c.Set("trace_id", requestID) + c.Header("X-Trace-ID", requestID) + c.Next() + } +} diff --git a/internal/server/server.go b/internal/server/server.go index 9be5e67..fdf04f6 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -3,6 +3,7 @@ package server import ( "EnGin/internal/global" "EnGin/internal/handler" + "EnGin/internal/middleware" "fmt" "github.com/gin-gonic/gin" @@ -13,6 +14,8 @@ func Run() { gin.SetMode(global.Config.Server.Mode) r := gin.New() r.Use(gin.Recovery()) + r.Use(middleware.TraceID()) + r.Use(middleware.Logger()) // 注册路由 handler.RegisterRoutes(r) diff --git a/internal/service/redis_ser/redis_jwt.go b/internal/service/redis_ser/redis_jwt.go new file mode 100644 index 0000000..13fe20b --- /dev/null +++ b/internal/service/redis_ser/redis_jwt.go @@ -0,0 +1,45 @@ +package redis_ser + +import ( + "EnGin/internal/db" + "EnGin/internal/global" + "EnGin/internal/utils/jwts" + "context" + "fmt" + "time" + + "go.uber.org/zap" +) + +// Logout 标记当前令牌已注销 (拉入黑名单) +func Logout(accessToken, refreshToken string) { + atoken, err := jwts.CheckAccessToken(accessToken) + rtoken, err := jwts.CheckRefreshToken(refreshToken) + if err != nil { + global.Log.Error("token验证失败", zap.Error(err)) + return + } + // 存储 access_token 黑名单 key + accessKey := fmt.Sprintf("logout_access_%s", accessToken) + // 存储 refresh_token 黑名单 key + refreshKey := fmt.Sprintf("logout_refresh_%s", refreshToken) + // 计算剩余有效期并作为 Redis 的过期时间缓存进去 + db.Redis.Set(context.Background(), accessKey, "", atoken.ExpiresAt.Sub(time.Now())) + db.Redis.Set(context.Background(), refreshKey, "", rtoken.ExpiresAt.Sub(time.Now())) +} + +// HasLogout 检查该 token 是否已经注销 +func HasLogout(accessToken string) bool { + // 如果 Redis 没有开启,默认放行 + if db.Redis == nil { + return false + } + + accessKey := fmt.Sprintf("logout_access_%s", accessToken) + _, err := db.Redis.Get(context.Background(), accessKey).Result() + if err == nil { + return true // 如果能查到 key,说明已经被注销 + } + + return false +} diff --git a/internal/utils/res/enter.go b/internal/utils/res/enter.go new file mode 100644 index 0000000..e84040a --- /dev/null +++ b/internal/utils/res/enter.go @@ -0,0 +1,146 @@ +package res + +import ( + "EnGin/internal/utils/validata" + + "github.com/gin-gonic/gin" +) + +// Code 自定义状态码结构 +type Code struct { + Code int + Msg string +} + +// Success 预定义的成功状态码 +var ( + Success = Code{0, "成功"} +) + +// 预定义的错误状态码 + +var ( + UnknownError = Code{1000, "未知错误"} + ParamError = Code{1001, "参数错误"} + UnauthorizedError = Code{1002, "未认证"} + ForbiddenError = Code{1003, "权限不足"} + NotFoundError = Code{1004, "资源不存在"} + DatabaseError = Code{1005, "数据库错误"} + ValidateError = Code{1006, "验证失败"} + ContentError = Code{1007, "内容不合法"} + DefaultError = Code{7, "失败"} // 默认错误状态码 +) + +type Response struct { + Code int `json:"code"` + Data any `json:"data"` + Msg string `json:"msg"` + TraceID string `json:"trace_id"` +} + +// 成功响应 + +func Ok(data any, code Code, msg string, c *gin.Context) { + message := code.Msg + if msg != "" { + message = msg + } + + // 获取 TraceID + traceID := c.GetString("trace_id") + + c.JSON(200, Response{ + Code: code.Code, + Data: data, + Msg: message, + TraceID: traceID, + }) +} + +func OkWithMsg(msg string, c *gin.Context) { + Ok(gin.H{}, Success, msg, c) +} + +func OkWithList(list any, count int64, c *gin.Context) { + Ok(map[string]any{ + "list": list, + "count": count, + }, Success, Success.Msg, c) +} + +func OkWithData(data any, c *gin.Context) { + Ok(data, Success, Success.Msg, c) +} + +// 失败响应 + +func Fail(errorCode Code, msg string, c *gin.Context) { + // 如果提供了特定的错误信息,则使用该信息,否则使用预定义的错误信息 + message := errorCode.Msg + if msg != "" { + message = msg + } + + statusCode := 200 + // 根据错误代码设置合适的HTTP状态码 + switch errorCode { + case ParamError: + statusCode = 400 // Bad Request + case UnauthorizedError: + statusCode = 401 // Unauthorized + case ForbiddenError: + statusCode = 403 // Forbidden + case NotFoundError: + statusCode = 404 // Not Found + case ValidateError, ContentError: + statusCode = 422 + case DatabaseError, UnknownError: + statusCode = 500 // Internal Server Error + + } + + // TraceID + traceID := c.GetString("trace_id") + + c.JSON(statusCode, Response{ + Code: errorCode.Code, + Data: gin.H{}, + Msg: message, + TraceID: traceID, + }) +} + +func FailWithMsg(msg string, c *gin.Context) { + Fail(DefaultError, msg, c) +} + +func FailWithError(err error, c *gin.Context) { + msg := validata.ValidateErr(err) + Fail(ValidateError, msg, c) +} + +// 添加专门的错误响应方法 + +func BadRequest(msg string, c *gin.Context) { + Fail(ParamError, msg, c) +} + +func Unauthorized(msg string, c *gin.Context) { + Fail(UnauthorizedError, msg, c) +} + +func Forbidden(msg string, c *gin.Context) { + Fail(ForbiddenError, msg, c) +} + +func NotFound(msg string, c *gin.Context) { + Fail(NotFoundError, msg, c) +} + +func InternalError(msg string, c *gin.Context) { + Fail(UnknownError, msg, c) +} + +func InvalidContent(msg string, c *gin.Context) { + Fail(ContentError, msg, c) +} diff --git a/internal/utils/validata/enter.go b/internal/utils/validata/enter.go new file mode 100644 index 0000000..58404ac --- /dev/null +++ b/internal/utils/validata/enter.go @@ -0,0 +1,48 @@ +package validata + +import ( + "errors" + "reflect" + "strings" + + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/locales/zh" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + zhtranslations "github.com/go-playground/validator/v10/translations/zh" +) + +var trans ut.Translator + +func init() { + // 创建翻译器 + uni := ut.New(zh.New()) + trans, _ = uni.GetTranslator("zh") + + // 注册翻译器 + v, ok := binding.Validator.Engine().(*validator.Validate) + if ok { + _ = zhtranslations.RegisterDefaultTranslations(v, trans) + } + v.RegisterTagNameFunc(func(field reflect.StructField) string { + label := field.Tag.Get("label") + if label == "" { + return field.Name + } + return label + }) +} + +func ValidateErr(err error) string { + var errs validator.ValidationErrors + if !errors.As(err, &errs) { + return err.Error() + } + + list := make([]string, 0, len(errs)) + for _, e := range errs { + list = append(list, e.Translate(trans)) + } + + return strings.Join(list, ";") +}