From 58fae2e09088c87a9921ddd286a49e005ece92b3 Mon Sep 17 00:00:00 2001 From: loveuer Date: Fri, 1 Nov 2024 17:47:33 +0800 Subject: [PATCH] dev: rbac --- internal/sqlType/error.go | 9 +++ internal/sqlType/jsonb.go | 76 ++++++++++++++++++++++ internal/sqlType/num_slice.go | 71 ++++++++++++++++++++ internal/sqlType/string_slice.go | 107 +++++++++++++++++++++++++++++++ model/privilege.go | 11 ++++ model/role.go | 13 ++++ model/scope.go | 11 ++++ model/user.go | 5 ++ 8 files changed, 303 insertions(+) create mode 100644 internal/sqlType/error.go create mode 100644 internal/sqlType/jsonb.go create mode 100644 internal/sqlType/num_slice.go create mode 100644 internal/sqlType/string_slice.go create mode 100644 model/privilege.go create mode 100644 model/role.go create mode 100644 model/scope.go diff --git a/internal/sqlType/error.go b/internal/sqlType/error.go new file mode 100644 index 0000000..d1195e0 --- /dev/null +++ b/internal/sqlType/error.go @@ -0,0 +1,9 @@ +package sqlType + +import "errors" + +var ( + ErrConvertScanVal = errors.New("convert scan val to str err") + ErrInvalidScanVal = errors.New("scan val invalid") + ErrConvertVal = errors.New("convert err") +) diff --git a/internal/sqlType/jsonb.go b/internal/sqlType/jsonb.go new file mode 100644 index 0000000..ace1417 --- /dev/null +++ b/internal/sqlType/jsonb.go @@ -0,0 +1,76 @@ +package sqlType + +import ( + "database/sql/driver" + "encoding/json" + + "github.com/jackc/pgtype" +) + +type JSONB struct { + Val pgtype.JSONB + Valid bool +} + +func NewJSONB(v interface{}) JSONB { + j := new(JSONB) + j.Val = pgtype.JSONB{} + if err := j.Val.Set(v); err == nil { + j.Valid = true + return *j + } + + return *j +} + +func (j *JSONB) Set(value interface{}) error { + if err := j.Val.Set(value); err != nil { + j.Valid = false + return err + } + + j.Valid = true + + return nil +} + +func (j *JSONB) Bind(model interface{}) error { + return j.Val.AssignTo(model) +} + +func (j *JSONB) Scan(value interface{}) error { + j.Val = pgtype.JSONB{} + if value == nil { + j.Valid = false + return nil + } + + j.Valid = true + + return j.Val.Scan(value) +} + +func (j JSONB) Value() (driver.Value, error) { + if j.Valid { + return j.Val.Value() + } + + return nil, nil +} + +func (j JSONB) MarshalJSON() ([]byte, error) { + if j.Valid { + return j.Val.MarshalJSON() + } + + return json.Marshal(nil) +} + +func (j *JSONB) UnmarshalJSON(b []byte) error { + if string(b) == "null" { + j.Valid = false + return j.Val.UnmarshalJSON(b) + } + + return j.Val.UnmarshalJSON(b) +} diff --git a/internal/sqlType/num_slice.go b/internal/sqlType/num_slice.go new file mode 100644 index 0000000..7bba191 --- /dev/null +++ b/internal/sqlType/num_slice.go @@ -0,0 +1,71 @@ +package sqlType + +import ( + "database/sql/driver" + "fmt" + "strconv" + "strings" + + "github.com/spf13/cast" +) + +type NumSlice[T ~int | ~int64 | ~uint | ~uint64] []T + +func (n *NumSlice[T]) Scan(val interface{}) error { + str, ok := val.(string) + if !ok { + return ErrConvertScanVal + } + + length := len(str) + + if length <= 0 { + *n = make(NumSlice[T], 0) + return nil + } + + if str[0] != '{' || str[length-1] != '}' { + return ErrInvalidScanVal + } + + str = str[1 : length-1] + if len(str) == 0 { + *n = make(NumSlice[T], 0) + return nil + } + + numStrs := strings.Split(str, ",") + nums := make([]T, len(numStrs)) + + for idx := range numStrs { + num, err := cast.ToInt64E(strings.TrimSpace(numStrs[idx])) + if err != nil { + return fmt.Errorf("%w: can't convert to %T", ErrConvertVal, T(0)) + } + + nums[idx] = T(num) + } + + *n = nums + + return nil +} + +func (n NumSlice[T]) Value() (driver.Value, error) { + if n == nil { + return "{}", nil + } + + if len(n) == 0 { + return "{}", nil + } + + ss := make([]string, 0, len(n)) + for idx := range n { + ss = append(ss, strconv.Itoa(int(n[idx]))) + } + + s := strings.Join(ss, ", ") + + return fmt.Sprintf("{%s}", s), nil +} diff --git a/internal/sqlType/string_slice.go b/internal/sqlType/string_slice.go new file mode 100644 index 0000000..be3ccfa --- /dev/null +++ b/internal/sqlType/string_slice.go @@ -0,0 +1,107 @@ +package sqlType + +import ( + "bytes" + "database/sql/driver" + "encoding/json" +) + +type StrSlice []string + +func (s *StrSlice) Scan(val interface{}) error { + + str, ok := val.(string) + if !ok { + return ErrConvertScanVal + } + + if len(str) < 2 { + return nil + } + + bs := make([]byte, 0, 128) + bss := make([]byte, 0, 2*len(str)) + + quoteCount := 0 + + for idx := 1; idx < len(str)-1; idx++ { + quote := str[idx] + switch quote { + case 44: + if quote == 44 && str[idx-1] != 92 && quoteCount == 0 { + if len(bs) > 0 { + if !(bs[0] == 34 && bs[len(bs)-1] == 34) { + bs = append([]byte{34}, bs...) + bs = append(bs, 34) + } + + bss = append(bss, bs...) + bss = append(bss, 44) + } + bs = bs[:0] + } else { + bs = append(bs, quote) + } + case 34: + if str[idx-1] != 92 { + quoteCount = (quoteCount + 1) % 2 + } + bs = append(bs, quote) + default: + bs = append(bs, quote) + } + + } + + if len(bs) > 0 { + if !(bs[0] == 34 && bs[len(bs)-1] == 34) { + bs = append([]byte{34}, bs...) + bs = append(bs, 34) + } + + bss = append(bss, bs...) + } else { + if len(bss) > 2 { + bss = bss[:len(bss)-2] + } + } + + bss = append([]byte{'['}, append(bss, ']')...) + + if err := json.Unmarshal(bss, s); err != nil { + return err + } + + return nil +} + +func (s StrSlice) Value() (driver.Value, error) { + if s == nil { + return "{}", nil + } + + if len(s) == 0 { + return "{}", nil + } + + buf := &bytes.Buffer{} + + encoder := json.NewEncoder(buf) + encoder.SetEscapeHTML(false) + + if err := encoder.Encode(s); err != nil { + return "{}", err + } + + bs := buf.Bytes() + + bs[0] = '{' + + if bs[len(bs)-1] == 10 { + bs = bs[:len(bs)-1] + } + + bs[len(bs)-1] = '}' + + return string(bs), nil +} diff --git a/model/privilege.go b/model/privilege.go new file mode 100644 index 0000000..092273d --- /dev/null +++ b/model/privilege.go @@ -0,0 +1,11 @@ +package model + +type Privilege struct { + CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` + UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` + DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"` + Code string `json:"code" gorm:"column:code;primaryKey"` + Label string `json:"label" gorm:"column:label"` + Parent string `json:"parent" gorm:"column:parent"` + Scope string `json:"scope" gorm:"column:scope"` +} diff --git a/model/role.go b/model/role.go new file mode 100644 index 0000000..29bab75 --- /dev/null +++ b/model/role.go @@ -0,0 +1,13 @@ +package model + +import "uauth/internal/sqlType" + +type Role struct { + CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` + UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` + DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"` + Code string `json:"code" gorm:"primaryKey;column:code"` + Label string `json:"label" gorm:"column:label"` + Parent string `json:"parent" gorm:"column:parent"` + PrivilegeCodes sqlType.StrSlice `json:"privilege_codes" gorm:"column:privilege_codes"` +} diff --git a/model/scope.go b/model/scope.go new file mode 100644 index 0000000..533bd3a --- /dev/null +++ b/model/scope.go @@ -0,0 +1,11 @@ +package model + +// 用户权限作用域 +type Scope struct { + Code string `json:"code" gorm:"column:code;type:varchar(8);not null;primaryKey"` + CreatedAt int64 `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"` + UpdatedAt int64 `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"` + DeletedAt int64 `json:"deleted_at" gorm:"index;column:deleted_at;default:0"` + Label string `json:"label" gorm:"column:label;type:varchar(64)"` + Parent string `json:"parent" gorm:"column:parent;type:varchar(8)"` +} diff --git a/model/user.go b/model/user.go index 26716c6..f7f00f1 100644 --- a/model/user.go +++ b/model/user.go @@ -6,6 +6,7 @@ import ( "github.com/loveuer/nf/nft/log" "time" "uauth/internal/opt" + "uauth/internal/sqlType" ) type Status int64 @@ -29,6 +30,9 @@ type User struct { CreatedByName string `json:"created_by_name" gorm:"column:created_by_name;type:varchar(64)"` LoginAt int64 `json:"login_at" gorm:"-"` + + Roles []*Role `json:"-" gorm:"-"` + RoleNames sqlType.StrSlice `json:"role_names" column:"role_names"` } func (u *User) JwtEncode() (token string, err error) { @@ -40,6 +44,7 @@ func (u *User) JwtEncode() (token string, err error) { "username": u.Username, "nickname": u.Nickname, "status": u.Status, + "avatar": u.Avatar, "login_at": now.UnixMilli(), })