diff --git a/Makefile b/Makefile index 1d51c21..6d0df19 100644 --- a/Makefile +++ b/Makefile @@ -47,3 +47,21 @@ build-docker: docker buildx build -t $(DOCKER_REPO):$(VERSION) --build-arg SSH_PRIVATE_KEY="$(cat ~/.ssh/ed_25519)" . rm -rf Dockerfile @echo "Generate core-api files successfully" + +.PHONY: gen-model +gen-model: # 建立 rpc 資料庫 + goctl model mysql ddl -c no -s ./generate/database/mysql/20230529020011_account_table.up.sql --style go_zero -d ./internal/model -i '' + goctl model mysql ddl -c no -s ./generate/database/mysql/20230529020011_account_uid_table.up.sql --style go_zero -d ./internal/model -i '' + goctl model mysql ddl -c no -s ./generate/database/mysql/20230529020011_user_table.up.sql --style go_zero -d ./internal/model -i '' + goctl model mysql ddl -c no -s ./generate/database/mysql/20230719061241_machine_node.up.sql --style go_zero -d ./internal/model -i '' + @echo "Generate model files successfully" + # 只產生 Model 剩下的要自己撰寫,連欄位名稱也是 + goctl model mongo -t AutoId -c --dir ./internal/model/mongo --style go_zero + +#goctl model mysql ddl -s ./generate/database/mysql/20240819090248_create_role_permission_table.up.sql --style go_zero -d ./internal/model -i '' (沒cache) +#goctl model mysql ddl -c no -s ./generate/database/mysql/20240816014305_create_permission_table.up.sql --style go_zero -d ./internal/model -i '' (有cache) + +#.PHONY: mock-gen +#mock-gen: # 建立 mock 資料 +# mockgen -source=./order.go -destination=../../repository/mock/order.go -package=mock +# @echo "Generate model files successfully" \ No newline at end of file diff --git a/generate/protobuf/service.proto b/generate/protobuf/service.proto index e9ab8a8..98a47fa 100644 --- a/generate/protobuf/service.proto +++ b/generate/protobuf/service.proto @@ -3,6 +3,11 @@ syntax = "proto3"; package member; option go_package="./member"; +// OKResp +message OKResp {} +// NoneReq +message NoneReq {} + // ================ enum ================ enum VerifyType { VERIFY_NONE = 0; // 初始(異常) @@ -27,11 +32,6 @@ enum MemberStatus { STATUS_GA = 6; // GA 已綁定 } -enum Gender { - GENDER_NONE = 0; // 初始(未提供) - GENDER_MALE = 1; // 男性 - GENDER_FEMALE = 2; // 女性 -} // ================ enum ================ @@ -41,17 +41,6 @@ message Pager { int64 size=2; int64 index=3; } - -message Response { - BaseResp status=1; -} - -message BaseResp { - string code = 1; - string message = 2; - string error = 3; -} - // ================ common ================ @@ -81,8 +70,7 @@ message CreateUserInfoReq { } message GetAccountInfoResp { - BaseResp status = 1; - CreateLoginUserReq data = 2; + CreateLoginUserReq data = 1; } // UpdateUserInfoReq 不處理邏輯給不給改,這裡只關新增修改刪除 @@ -91,24 +79,18 @@ message UpdateUserInfoReq { optional string language = 2; optional string currency = 3; optional string nick_name = 4; - optional uint32 gender = 5; - optional int64 birthday = 6; - optional VerifyType verify_type = 7; - optional AlarmType alarm_type = 8; - optional MemberStatus status = 9; + optional VerifyType verify_type = 5; + optional AlarmType alarm_type = 6; + optional MemberStatus status = 7; } message GetUIDByAccountReq { string account = 1; } -message UID { - string uid = 1; -} - message GetUidByAccountResp { - BaseResp status = 1; - UID data = 2; + string uid = 1; + string account =2; } message UpdateTokenReq { @@ -126,8 +108,7 @@ message VerifyCode { } message GenerateRefreshCodeResp { - BaseResp status = 1; - VerifyCode data = 2; + VerifyCode data = 1; } message VerifyRefreshCodeReq { @@ -158,8 +139,7 @@ message UserInfo { } message GetUserInfoResp { - BaseResp status = 1; - UserInfo data = 2; + UserInfo data = 1; } message ListUserInfoReq { @@ -173,29 +153,28 @@ message ListUserInfoReq { } message ListUserInfoResp { - BaseResp status = 1; - repeated UserInfo data = 2; - Pager page =3; + repeated UserInfo data = 1; + Pager page =2; } service Account { // CreateUserAccount 建立帳號與密碼 -> 可登入,但可不可以做其他事情看業務流程,也可以只註冊就好 - rpc CreateUserAccount(CreateLoginUserReq) returns(Response); + rpc CreateUserAccount(CreateLoginUserReq) returns(OKResp); // GetUserAccountInfo 取得帳號密碼資料 rpc GetUserAccountInfo(GetUIDByAccountReq) returns(GetAccountInfoResp); // UpdateUserToken 更新密碼 - rpc UpdateUserToken(UpdateTokenReq) returns(Response); + rpc UpdateUserToken(UpdateTokenReq) returns(OKResp); // GetUidByAccount 用帳號換取 UID rpc GetUidByAccount(GetUIDByAccountReq) returns(GetUidByAccountResp); // BindAccount 綁定帳號 -> account bind to UID - rpc BindAccount(BindingUserReq) returns(Response); + rpc BindAccount(BindingUserReq) returns(OKResp); // BindUserInfo 初次,綁定 User Info - rpc BindUserInfo(CreateUserInfoReq) returns(Response); + rpc BindUserInfo(CreateUserInfoReq) returns(OKResp); // UpdateUserInfo 更新 User Info - rpc UpdateUserInfo(UpdateUserInfoReq) returns(Response); + rpc UpdateUserInfo(UpdateUserInfoReq) returns(OKResp); // UpdateStatus 修改狀態 - rpc UpdateStatus(UpdateStatusReq) returns(Response); + rpc UpdateStatus(UpdateStatusReq) returns(OKResp); // GetUserInfo 取得會員資訊 rpc GetUserInfo(GetUserInfoReq) returns(GetUserInfoResp); // ListMember 取得會員列表 @@ -203,7 +182,7 @@ service Account { // GenerateRefreshCode 這個帳號驗證碼(十分鐘),通用的 rpc GenerateRefreshCode(GenerateRefreshCodeReq) returns(GenerateRefreshCodeResp); // VerifyRefreshCode 驗證忘記密碼 token - rpc VerifyRefreshCode(VerifyRefreshCodeReq) returns(Response); + rpc VerifyRefreshCode(VerifyRefreshCodeReq) returns(OKResp); } // ================ account ================ \ No newline at end of file diff --git a/go.mod b/go.mod index d075de8..13b294d 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,17 @@ module app-cloudep-member-server go 1.22.3 require ( + code.30cm.net/digimon/library-go/errors v1.0.1 + code.30cm.net/digimon/library-go/validator v1.0.0 + github.com/bwmarrin/snowflake v0.3.0 github.com/zeromicro/go-zero v1.7.0 + go.uber.org/goleak v1.3.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -18,11 +23,16 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/fatih/color v1.17.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.4 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -33,6 +43,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -65,6 +76,7 @@ require ( go.uber.org/automaxprocs v1.5.3 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect golang.org/x/sys v0.22.0 // indirect diff --git a/internal/config/config.go b/internal/config/config.go index c1f85b9..696a3f7 100755 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,7 +1,33 @@ package config -import "github.com/zeromicro/go-zero/zrpc" +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/redis" + "github.com/zeromicro/go-zero/zrpc" +) type Config struct { zrpc.RpcServerConf + // 加上 DB 結構體 + DB struct { + DsnString string + } + // 快取 + Cache cache.CacheConf + // 密碼加密層數 + Bcrypt struct { + Cost int + } + // Redis Cluster + RedisCluster redis.RedisConf + + Mongo struct { + Schema string + User string + Password string + Host string + Port string + Database string + Collection string + } } diff --git a/internal/domain/const.go b/internal/domain/const.go new file mode 100644 index 0000000..5279edd --- /dev/null +++ b/internal/domain/const.go @@ -0,0 +1,18 @@ +package domain + +const ( + DefaultPageSize = 100 + DefaultPageIndex = 1 + Scope = 10 +) + +const InitAutoId = 1000000 +const DefaultReferralCodeLen = 8 + +var ConvertTable = [...]string{ + "O", "D", "W", "X", "Y", + "G", "B", "C", "H", "E", + "F", "A", "Q", "I", "J", + "L", "M", "N", "Z", "K", + "P", "V", "R", "S", "T", +} diff --git a/internal/domain/usecase/errors.go b/internal/domain/usecase/errors.go new file mode 100644 index 0000000..0455dfa --- /dev/null +++ b/internal/domain/usecase/errors.go @@ -0,0 +1,21 @@ +package usecase + +import ( + ers "code.30cm.net/digimon/library-go/errors" + "code.30cm.net/digimon/library-go/errors/code" +) + +// 12 represents Scope +// 100 represents Category +// 9 represents Detail error code +// full code 12009 只會有 系統以及錯誤碼,category 是給系統判定用的 +// 目前 Scope 以及分類要系統共用,係向的錯誤各自服務實作就好 + +const ( + UIDOutOfRangeErrorCode = iota + 1 +) + +// UIDOutOfRangeErrorCodeError 20001 Token 簽名錯誤 +func UIDOutOfRangeErrorCodeError(msg string) *ers.LibError { + return ers.NewErr(code.CloudEPMember, code.CatInput, UIDOutOfRangeErrorCode, msg) +} diff --git a/internal/domain/usecase/uid_generate.go b/internal/domain/usecase/uid_generate.go new file mode 100644 index 0000000..3f9bbb8 --- /dev/null +++ b/internal/domain/usecase/uid_generate.go @@ -0,0 +1,7 @@ +package usecase + +import "context" + +type UIDGenerateUseCase interface { + Generate(ctx context.Context) (string, error) +} diff --git a/internal/lib/middleware/with_context.go b/internal/lib/middleware/with_context.go new file mode 100644 index 0000000..39d8a05 --- /dev/null +++ b/internal/lib/middleware/with_context.go @@ -0,0 +1,27 @@ +package middleware + +import ( + ers "code.30cm.net/digimon/library-go/errors" + "context" + "errors" + "github.com/zeromicro/go-zero/core/logx" + "google.golang.org/grpc" + "time" +) + +const defaultTimeout = 30 * time.Second + +func TimeoutMiddleware(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) { + + newCtx, cancelCtx := context.WithTimeout(ctx, defaultTimeout) + defer func() { + cancelCtx() + + if errors.Is(newCtx.Err(), context.DeadlineExceeded) { + err = ers.SystemTimeoutError(info.FullMethod) + logx.Errorf("Method: %s, request %v, timeout: %d", info.FullMethod, req, defaultTimeout) + } + }() + + return handler(ctx, req) +} diff --git a/internal/lib/snackflow/error.go b/internal/lib/snackflow/error.go new file mode 100644 index 0000000..45327f2 --- /dev/null +++ b/internal/lib/snackflow/error.go @@ -0,0 +1,18 @@ +package snowflake + +import ( + "fmt" + "time" +) + +type NewNodeError struct { + machineNodeID int64 + startTime time.Time + + Err error +} + +func (e *NewNodeError) Error() string { + return fmt.Sprintf("new node fail machineNodeID: %d, startTime: %s, err: %v", + e.machineNodeID, e.startTime, e.Err) +} diff --git a/internal/lib/snackflow/error_test.go b/internal/lib/snackflow/error_test.go new file mode 100644 index 0000000..48c64b4 --- /dev/null +++ b/internal/lib/snackflow/error_test.go @@ -0,0 +1,54 @@ +package snowflake + +import ( + "fmt" + "testing" + "time" +) + +func TestNewNodeError_Error(t *testing.T) { + startTime, err := time.Parse(time.DateOnly, "2023-07-20") + if err != nil { + t.Error(err) + } + + t.Parallel() + + type fields struct { + machineNodeID int64 + startTime time.Time + Err error + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "success", + fields: fields{ + machineNodeID: 1, + startTime: startTime, + Err: nil, + }, + want: fmt.Sprintf("new node fail machineNodeID: %d, startTime: %s, err: %v", + 1, "2023-07-20 00:00:00 +0000 UTC", nil), + }, + } + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + e := &NewNodeError{ + machineNodeID: tt.fields.machineNodeID, + startTime: tt.fields.startTime, + Err: tt.fields.Err, + } + if got := e.Error(); got != tt.want { + t.Errorf("Error() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/lib/snackflow/readme.md b/internal/lib/snackflow/readme.md new file mode 100644 index 0000000..bdf2d8d --- /dev/null +++ b/internal/lib/snackflow/readme.md @@ -0,0 +1,8 @@ +### +# snowflake + +```go +import "yt.com/backend/common.git/snowflake" +``` + +### 量級超過1024 再來做解決 \ No newline at end of file diff --git a/internal/lib/snackflow/snowflake.go b/internal/lib/snackflow/snowflake.go new file mode 100644 index 0000000..09f3fb6 --- /dev/null +++ b/internal/lib/snackflow/snowflake.go @@ -0,0 +1,81 @@ +package snowflake + +import ( + "fmt" + "sync" + "time" + + "github.com/bwmarrin/snowflake" +) + +var mu sync.Mutex + +// Snowflake provides a way to NewNode for Generate UID. +type Snowflake struct { + machineNodeID int64 + + startTime time.Time +} + +// Option is the options type to configure Snowflake. +type Option func(*Snowflake) + +// New returns a new Snowflake instance with the provided options. +func New(opts ...Option) *Snowflake { + s := &Snowflake{ + // default machine 1 + machineNodeID: 1, + } + + for _, opt := range opts { + opt(s) + } + + return s +} + +// WithMachineNodeID adds machineID total 10bit = 1024 machine number. +func WithMachineNodeID(machineNodeID int64) Option { + return func(snowflake *Snowflake) { + snowflake.machineNodeID = machineNodeID + } +} + +// WithStartTime adds snowflake start timestamp in milliseconds. +func WithStartTime(startTime time.Time) Option { + return func(snowflake *Snowflake) { + snowflake.startTime = startTime + } +} + +// GetNowDate return nowTodayDate e.g. 2023-07-20 00:00:00 +0000 UTC. +func GetNowDate() (time.Time, error) { + startTime := time.Now().UTC().Format(time.DateOnly) + + st, err := time.Parse(time.DateOnly, startTime) + if err != nil { + return time.Time{}, fmt.Errorf("time.Parse failed :%w", err) + } + + return st, nil +} + +// NewNode return snowflake node use Generate UID. +func (s *Snowflake) NewNode() (*snowflake.Node, error) { + mu.Lock() + defer mu.Unlock() + + snowflake.Epoch = s.startTime.UnixMilli() + + node, err := snowflake.NewNode(s.machineNodeID) + if err != nil { + return nil, fmt.Errorf("snowflake.NewNode, failed :%w", + &NewNodeError{ + machineNodeID: s.machineNodeID, + startTime: s.startTime, + Err: err, + }) + } + + return node, nil +} diff --git a/internal/lib/snackflow/snowflake_test.go b/internal/lib/snackflow/snowflake_test.go new file mode 100644 index 0000000..2b477af --- /dev/null +++ b/internal/lib/snackflow/snowflake_test.go @@ -0,0 +1,148 @@ +package snowflake + +import ( + "flag" + "os" + "reflect" + "testing" + "time" + + "go.uber.org/goleak" +) + +func TestMain(m *testing.M) { + leak := flag.Bool("leak", false, "use leak detector") + flag.Parse() + + if *leak { + goleak.VerifyTestMain(m) + + return + } + + os.Exit(m.Run()) +} + +func TestSnowflake(t *testing.T) { + st, err := GetNowDate() + if err != nil { + t.Error(err) + } + + t.Parallel() + + type args struct { + machineNodeID int64 + startTime time.Time + } + tests := []struct { + name string + args args + want *Snowflake + + wantDeepEqualErr bool + wantNewNodeErr bool + }{ + { + name: "success", + args: args{ + machineNodeID: 10, + startTime: st, + }, + want: &Snowflake{ + machineNodeID: 10, + startTime: st, + }, + wantDeepEqualErr: false, + wantNewNodeErr: false, + }, + { + name: "failed machine node ID negative number", + args: args{ + machineNodeID: -1, + startTime: time.Time{}, + }, + want: &Snowflake{ + machineNodeID: -1, + startTime: time.Time{}, + }, + wantDeepEqualErr: false, + wantNewNodeErr: true, + }, + { + name: "failed snowflake struct field by machine node ID", + args: args{ + machineNodeID: 10, + startTime: st, + }, + want: &Snowflake{ + machineNodeID: 2, + startTime: st, + }, + wantDeepEqualErr: true, + wantNewNodeErr: false, + }, + { + name: "failed snowflake struct field by startTime", + args: args{ + machineNodeID: 2, + startTime: st, + }, + want: &Snowflake{ + machineNodeID: 2, + startTime: time.Time{}, + }, + wantDeepEqualErr: true, + wantNewNodeErr: false, + }, + } + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got := New( + WithMachineNodeID(tt.args.machineNodeID), + WithStartTime(tt.args.startTime), + ) + + if !reflect.DeepEqual(got, tt.want) != tt.wantDeepEqualErr { + t.Errorf("Snowflake.New() = %v, want %v", got, tt.want) + } + + node, err := got.NewNode() + if (err != nil) != tt.wantNewNodeErr { + t.Errorf("NewNode() = %v, want %v", err != nil, tt.wantNewNodeErr) + } + + if err == nil { + id := node.Generate().Int64() + if id <= 0 { + t.Errorf("node.Generate().Int64() = %v, want %s", id, "id > 0") + } + } + }) + } +} + +func BenchmarkSnowflake(b *testing.B) { + st, err := GetNowDate() + if err != nil { + b.Error(err) + } + + snowflake := New( + WithMachineNodeID(1), + WithStartTime(st), + ) + + node, err := snowflake.NewNode() + if err != nil { + b.Error(err) + } + + for i := 0; i < b.N; i++ { + node.Generate().Int64() + } +} diff --git a/internal/model/account_model.go b/internal/model/account_model.go new file mode 100755 index 0000000..958d6dc --- /dev/null +++ b/internal/model/account_model.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ AccountModel = (*customAccountModel)(nil) + +type ( + // AccountModel is an interface to be customized, add more methods here, + // and implement the added methods in customAccountModel. + AccountModel interface { + accountModel + } + + customAccountModel struct { + *defaultAccountModel + } +) + +// NewAccountModel returns a model for the database table. +func NewAccountModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) AccountModel { + return &customAccountModel{ + defaultAccountModel: newAccountModel(conn, c, opts...), + } +} diff --git a/internal/model/account_model_gen.go b/internal/model/account_model_gen.go new file mode 100755 index 0000000..97c22bf --- /dev/null +++ b/internal/model/account_model_gen.go @@ -0,0 +1,154 @@ +// Code generated by goctl. DO NOT EDIT. + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + accountFieldNames = builder.RawFieldNames(&Account{}) + accountRows = strings.Join(accountFieldNames, ",") + accountRowsExpectAutoSet = strings.Join(stringx.Remove(accountFieldNames, "`id`"), ",") + accountRowsWithPlaceHolder = strings.Join(stringx.Remove(accountFieldNames, "`id`"), "=?,") + "=?" + + cacheAccountIdPrefix = "cache:account:id:" + cacheAccountAccountPrefix = "cache:account:account:" +) + +type ( + accountModel interface { + Insert(ctx context.Context, data *Account) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*Account, error) + FindOneByAccount(ctx context.Context, account string) (*Account, error) + Update(ctx context.Context, data *Account) error + Delete(ctx context.Context, id int64) error + } + + defaultAccountModel struct { + sqlc.CachedConn + table string + } + + Account struct { + Id int64 `db:"id"` + Account string `db:"account"` + Token string `db:"token"` + Platform int64 `db:"platform"` // 平台類型 1. ark 2. google + CreateTime int64 `db:"create_time"` + UpdateTime int64 `db:"update_time"` + } +) + +func newAccountModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultAccountModel { + return &defaultAccountModel{ + CachedConn: sqlc.NewConn(conn, c, opts...), + table: "`account`", + } +} + +func (m *defaultAccountModel) withSession(session sqlx.Session) *defaultAccountModel { + return &defaultAccountModel{ + CachedConn: m.CachedConn.WithSession(session), + table: "`account`", + } +} + +func (m *defaultAccountModel) Delete(ctx context.Context, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + accountAccountKey := fmt.Sprintf("%s%v", cacheAccountAccountPrefix, data.Account) + accountIdKey := fmt.Sprintf("%s%v", cacheAccountIdPrefix, id) + _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + return conn.ExecCtx(ctx, query, id) + }, accountAccountKey, accountIdKey) + return err +} + +func (m *defaultAccountModel) FindOne(ctx context.Context, id int64) (*Account, error) { + accountIdKey := fmt.Sprintf("%s%v", cacheAccountIdPrefix, id) + var resp Account + err := m.QueryRowCtx(ctx, &resp, accountIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", accountRows, m.table) + return conn.QueryRowCtx(ctx, v, query, id) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultAccountModel) FindOneByAccount(ctx context.Context, account string) (*Account, error) { + accountAccountKey := fmt.Sprintf("%s%v", cacheAccountAccountPrefix, account) + var resp Account + err := m.QueryRowIndexCtx(ctx, &resp, accountAccountKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) { + query := fmt.Sprintf("select %s from %s where `account` = ? limit 1", accountRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, account); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultAccountModel) Insert(ctx context.Context, data *Account) (sql.Result, error) { + accountAccountKey := fmt.Sprintf("%s%v", cacheAccountAccountPrefix, data.Account) + accountIdKey := fmt.Sprintf("%s%v", cacheAccountIdPrefix, data.Id) + ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?)", m.table, accountRowsExpectAutoSet) + return conn.ExecCtx(ctx, query, data.Account, data.Token, data.Platform, data.CreateTime, data.UpdateTime) + }, accountAccountKey, accountIdKey) + return ret, err +} + +func (m *defaultAccountModel) Update(ctx context.Context, newData *Account) error { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return err + } + + accountAccountKey := fmt.Sprintf("%s%v", cacheAccountAccountPrefix, data.Account) + accountIdKey := fmt.Sprintf("%s%v", cacheAccountIdPrefix, data.Id) + _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, accountRowsWithPlaceHolder) + return conn.ExecCtx(ctx, query, newData.Account, newData.Token, newData.Platform, newData.CreateTime, newData.UpdateTime, newData.Id) + }, accountAccountKey, accountIdKey) + return err +} + +func (m *defaultAccountModel) formatPrimary(primary any) string { + return fmt.Sprintf("%s%v", cacheAccountIdPrefix, primary) +} + +func (m *defaultAccountModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", accountRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary) +} + +func (m *defaultAccountModel) tableName() string { + return m.table +} diff --git a/internal/model/account_to_uid_model.go b/internal/model/account_to_uid_model.go new file mode 100755 index 0000000..095457e --- /dev/null +++ b/internal/model/account_to_uid_model.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ AccountToUidModel = (*customAccountToUidModel)(nil) + +type ( + // AccountToUidModel is an interface to be customized, add more methods here, + // and implement the added methods in customAccountToUidModel. + AccountToUidModel interface { + accountToUidModel + } + + customAccountToUidModel struct { + *defaultAccountToUidModel + } +) + +// NewAccountToUidModel returns a model for the database table. +func NewAccountToUidModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) AccountToUidModel { + return &customAccountToUidModel{ + defaultAccountToUidModel: newAccountToUidModel(conn, c, opts...), + } +} diff --git a/internal/model/account_to_uid_model_gen.go b/internal/model/account_to_uid_model_gen.go new file mode 100755 index 0000000..75aea24 --- /dev/null +++ b/internal/model/account_to_uid_model_gen.go @@ -0,0 +1,152 @@ +// Code generated by goctl. DO NOT EDIT. + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + accountToUidFieldNames = builder.RawFieldNames(&AccountToUid{}) + accountToUidRows = strings.Join(accountToUidFieldNames, ",") + accountToUidRowsExpectAutoSet = strings.Join(stringx.Remove(accountToUidFieldNames, "`id`"), ",") + accountToUidRowsWithPlaceHolder = strings.Join(stringx.Remove(accountToUidFieldNames, "`id`"), "=?,") + "=?" + + cacheAccountToUidIdPrefix = "cache:accountToUid:id:" + cacheAccountToUidAccountPrefix = "cache:accountToUid:account:" +) + +type ( + accountToUidModel interface { + Insert(ctx context.Context, data *AccountToUid) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*AccountToUid, error) + FindOneByAccount(ctx context.Context, account string) (*AccountToUid, error) + Update(ctx context.Context, data *AccountToUid) error + Delete(ctx context.Context, id int64) error + } + + defaultAccountToUidModel struct { + sqlc.CachedConn + table string + } + + AccountToUid struct { + Id int64 `db:"id"` + Account string `db:"account"` + Uid string `db:"uid"` + Type int64 `db:"type"` // 1 手機 2 信箱 3 自定義帳號 + } +) + +func newAccountToUidModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultAccountToUidModel { + return &defaultAccountToUidModel{ + CachedConn: sqlc.NewConn(conn, c, opts...), + table: "`account_to_uid`", + } +} + +func (m *defaultAccountToUidModel) withSession(session sqlx.Session) *defaultAccountToUidModel { + return &defaultAccountToUidModel{ + CachedConn: m.CachedConn.WithSession(session), + table: "`account_to_uid`", + } +} + +func (m *defaultAccountToUidModel) Delete(ctx context.Context, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + accountToUidAccountKey := fmt.Sprintf("%s%v", cacheAccountToUidAccountPrefix, data.Account) + accountToUidIdKey := fmt.Sprintf("%s%v", cacheAccountToUidIdPrefix, id) + _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + return conn.ExecCtx(ctx, query, id) + }, accountToUidAccountKey, accountToUidIdKey) + return err +} + +func (m *defaultAccountToUidModel) FindOne(ctx context.Context, id int64) (*AccountToUid, error) { + accountToUidIdKey := fmt.Sprintf("%s%v", cacheAccountToUidIdPrefix, id) + var resp AccountToUid + err := m.QueryRowCtx(ctx, &resp, accountToUidIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", accountToUidRows, m.table) + return conn.QueryRowCtx(ctx, v, query, id) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultAccountToUidModel) FindOneByAccount(ctx context.Context, account string) (*AccountToUid, error) { + accountToUidAccountKey := fmt.Sprintf("%s%v", cacheAccountToUidAccountPrefix, account) + var resp AccountToUid + err := m.QueryRowIndexCtx(ctx, &resp, accountToUidAccountKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) { + query := fmt.Sprintf("select %s from %s where `account` = ? limit 1", accountToUidRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, account); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultAccountToUidModel) Insert(ctx context.Context, data *AccountToUid) (sql.Result, error) { + accountToUidAccountKey := fmt.Sprintf("%s%v", cacheAccountToUidAccountPrefix, data.Account) + accountToUidIdKey := fmt.Sprintf("%s%v", cacheAccountToUidIdPrefix, data.Id) + ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?)", m.table, accountToUidRowsExpectAutoSet) + return conn.ExecCtx(ctx, query, data.Account, data.Uid, data.Type) + }, accountToUidAccountKey, accountToUidIdKey) + return ret, err +} + +func (m *defaultAccountToUidModel) Update(ctx context.Context, newData *AccountToUid) error { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return err + } + + accountToUidAccountKey := fmt.Sprintf("%s%v", cacheAccountToUidAccountPrefix, data.Account) + accountToUidIdKey := fmt.Sprintf("%s%v", cacheAccountToUidIdPrefix, data.Id) + _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, accountToUidRowsWithPlaceHolder) + return conn.ExecCtx(ctx, query, newData.Account, newData.Uid, newData.Type, newData.Id) + }, accountToUidAccountKey, accountToUidIdKey) + return err +} + +func (m *defaultAccountToUidModel) formatPrimary(primary any) string { + return fmt.Sprintf("%s%v", cacheAccountToUidIdPrefix, primary) +} + +func (m *defaultAccountToUidModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", accountToUidRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary) +} + +func (m *defaultAccountToUidModel) tableName() string { + return m.table +} diff --git a/internal/model/machine_node_model.go b/internal/model/machine_node_model.go new file mode 100755 index 0000000..0bd7cd2 --- /dev/null +++ b/internal/model/machine_node_model.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ MachineNodeModel = (*customMachineNodeModel)(nil) + +type ( + // MachineNodeModel is an interface to be customized, add more methods here, + // and implement the added methods in customMachineNodeModel. + MachineNodeModel interface { + machineNodeModel + } + + customMachineNodeModel struct { + *defaultMachineNodeModel + } +) + +// NewMachineNodeModel returns a model for the database table. +func NewMachineNodeModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) MachineNodeModel { + return &customMachineNodeModel{ + defaultMachineNodeModel: newMachineNodeModel(conn, c, opts...), + } +} diff --git a/internal/model/machine_node_model_gen.go b/internal/model/machine_node_model_gen.go new file mode 100755 index 0000000..37d29b5 --- /dev/null +++ b/internal/model/machine_node_model_gen.go @@ -0,0 +1,117 @@ +// Code generated by goctl. DO NOT EDIT. + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + machineNodeFieldNames = builder.RawFieldNames(&MachineNode{}) + machineNodeRows = strings.Join(machineNodeFieldNames, ",") + machineNodeRowsExpectAutoSet = strings.Join(stringx.Remove(machineNodeFieldNames, "`id`"), ",") + machineNodeRowsWithPlaceHolder = strings.Join(stringx.Remove(machineNodeFieldNames, "`id`"), "=?,") + "=?" + + cacheMachineNodeIdPrefix = "cache:machineNode:id:" +) + +type ( + machineNodeModel interface { + Insert(ctx context.Context, data *MachineNode) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*MachineNode, error) + Update(ctx context.Context, data *MachineNode) error + Delete(ctx context.Context, id int64) error + } + + defaultMachineNodeModel struct { + sqlc.CachedConn + table string + } + + MachineNode struct { + Id int64 `db:"id"` // 流水號 + CreateTime int64 `db:"create_time"` // 創建時間 + UpdateTime int64 `db:"update_time"` // 更新時間 + HostName string `db:"host_name"` // host name + } +) + +func newMachineNodeModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultMachineNodeModel { + return &defaultMachineNodeModel{ + CachedConn: sqlc.NewConn(conn, c, opts...), + table: "`machine_node`", + } +} + +func (m *defaultMachineNodeModel) withSession(session sqlx.Session) *defaultMachineNodeModel { + return &defaultMachineNodeModel{ + CachedConn: m.CachedConn.WithSession(session), + table: "`machine_node`", + } +} + +func (m *defaultMachineNodeModel) Delete(ctx context.Context, id int64) error { + machineNodeIdKey := fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, id) + _, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + return conn.ExecCtx(ctx, query, id) + }, machineNodeIdKey) + return err +} + +func (m *defaultMachineNodeModel) FindOne(ctx context.Context, id int64) (*MachineNode, error) { + machineNodeIdKey := fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, id) + var resp MachineNode + err := m.QueryRowCtx(ctx, &resp, machineNodeIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", machineNodeRows, m.table) + return conn.QueryRowCtx(ctx, v, query, id) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultMachineNodeModel) Insert(ctx context.Context, data *MachineNode) (sql.Result, error) { + machineNodeIdKey := fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, data.Id) + ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?)", m.table, machineNodeRowsExpectAutoSet) + return conn.ExecCtx(ctx, query, data.CreateTime, data.UpdateTime, data.HostName) + }, machineNodeIdKey) + return ret, err +} + +func (m *defaultMachineNodeModel) Update(ctx context.Context, data *MachineNode) error { + machineNodeIdKey := fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, data.Id) + _, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, machineNodeRowsWithPlaceHolder) + return conn.ExecCtx(ctx, query, data.CreateTime, data.UpdateTime, data.HostName, data.Id) + }, machineNodeIdKey) + return err +} + +func (m *defaultMachineNodeModel) formatPrimary(primary any) string { + return fmt.Sprintf("%s%v", cacheMachineNodeIdPrefix, primary) +} + +func (m *defaultMachineNodeModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", machineNodeRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary) +} + +func (m *defaultMachineNodeModel) tableName() string { + return m.table +} diff --git a/internal/model/mongo/auto_id_model.go b/internal/model/mongo/auto_id_model.go new file mode 100644 index 0000000..bd5d619 --- /dev/null +++ b/internal/model/mongo/auto_id_model.go @@ -0,0 +1,46 @@ +package model + +import ( + "context" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/monc" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo/options" +) + +var _ AutoIdModel = (*customAutoIdModel)(nil) + +type ( + // AutoIdModel is an interface to be customized, add more methods here, + // and implement the added methods in customAutoIdModel. + AutoIdModel interface { + autoIdModel + Inc(ctx context.Context, data *AutoId) error + } + + customAutoIdModel struct { + *defaultAutoIdModel + } +) + +// NewAutoIdModel returns a model for the mongo. +func NewAutoIdModel(url, db, collection string, c cache.CacheConf) AutoIdModel { + conn := monc.MustNewModel(url, db, collection, c) + return &customAutoIdModel{ + defaultAutoIdModel: newDefaultAutoIdModel(conn), + } +} + +func (m *customAutoIdModel) Inc(ctx context.Context, data *AutoId) error { + filter := bson.M{"admin": "auto_id"} + update := bson.M{"$inc": bson.M{"counter": 1}} + + key := prefixAutoIdCacheKey + "count" + + err := m.conn.FindOneAndUpdate(ctx, key, data, filter, update, + options.FindOneAndUpdate().SetUpsert(true), + options.FindOneAndUpdate().SetReturnDocument(options.After), + ) + + return err +} diff --git a/internal/model/mongo/auto_id_model_gen.go b/internal/model/mongo/auto_id_model_gen.go new file mode 100644 index 0000000..5163d74 --- /dev/null +++ b/internal/model/mongo/auto_id_model_gen.go @@ -0,0 +1,77 @@ +// Code generated by goctl. DO NOT EDIT. +package model + +import ( + "context" + "time" + + "github.com/zeromicro/go-zero/core/stores/monc" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +var prefixAutoIdCacheKey = "cache:autoId:" + +type autoIdModel interface { + Insert(ctx context.Context, data *AutoId) error + FindOne(ctx context.Context, id string) (*AutoId, error) + Update(ctx context.Context, data *AutoId) (*mongo.UpdateResult, error) + Delete(ctx context.Context, id string) (int64, error) +} + +type defaultAutoIdModel struct { + conn *monc.Model +} + +func newDefaultAutoIdModel(conn *monc.Model) *defaultAutoIdModel { + return &defaultAutoIdModel{conn: conn} +} + +func (m *defaultAutoIdModel) Insert(ctx context.Context, data *AutoId) error { + if data.ID.IsZero() { + data.ID = primitive.NewObjectID() + data.CreateAt = time.Now() + data.UpdateAt = time.Now() + } + + key := prefixAutoIdCacheKey + data.ID.Hex() + _, err := m.conn.InsertOne(ctx, key, data) + return err +} + +func (m *defaultAutoIdModel) FindOne(ctx context.Context, id string) (*AutoId, error) { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, ErrInvalidObjectId + } + + var data AutoId + key := prefixAutoIdCacheKey + id + err = m.conn.FindOne(ctx, key, &data, bson.M{"_id": oid}) + switch err { + case nil: + return &data, nil + case monc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultAutoIdModel) Update(ctx context.Context, data *AutoId) (*mongo.UpdateResult, error) { + data.UpdateAt = time.Now() + key := prefixAutoIdCacheKey + data.ID.Hex() + res, err := m.conn.UpdateOne(ctx, key, bson.M{"_id": data.ID}, bson.M{"$set": data}) + return res, err +} + +func (m *defaultAutoIdModel) Delete(ctx context.Context, id string) (int64, error) { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return 0, ErrInvalidObjectId + } + key := prefixAutoIdCacheKey + id + res, err := m.conn.DeleteOne(ctx, key, bson.M{"_id": oid}) + return res, err +} diff --git a/internal/model/mongo/auto_id_types.go b/internal/model/mongo/auto_id_types.go new file mode 100644 index 0000000..3605d9d --- /dev/null +++ b/internal/model/mongo/auto_id_types.go @@ -0,0 +1,15 @@ +package model + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type AutoId struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Name string `bson:"name,omitempty" json:"name,omitempty"` + Count uint64 `bson:"count,omitempty" json:"count,omitempty"` + UpdateAt time.Time `bson:"updateAt,omitempty" json:"updateAt,omitempty"` + CreateAt time.Time `bson:"createAt,omitempty" json:"createAt,omitempty"` +} diff --git a/internal/model/mongo/error.go b/internal/model/mongo/error.go new file mode 100644 index 0000000..27d9244 --- /dev/null +++ b/internal/model/mongo/error.go @@ -0,0 +1,12 @@ +package model + +import ( + "errors" + + "github.com/zeromicro/go-zero/core/stores/mon" +) + +var ( + ErrNotFound = mon.ErrNotFound + ErrInvalidObjectId = errors.New("invalid objectId") +) diff --git a/internal/model/user_table_model.go b/internal/model/user_table_model.go new file mode 100755 index 0000000..13f2b1d --- /dev/null +++ b/internal/model/user_table_model.go @@ -0,0 +1,27 @@ +package model + +import ( + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) + +var _ UserTableModel = (*customUserTableModel)(nil) + +type ( + // UserTableModel is an interface to be customized, add more methods here, + // and implement the added methods in customUserTableModel. + UserTableModel interface { + userTableModel + } + + customUserTableModel struct { + *defaultUserTableModel + } +) + +// NewUserTableModel returns a model for the database table. +func NewUserTableModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) UserTableModel { + return &customUserTableModel{ + defaultUserTableModel: newUserTableModel(conn, c, opts...), + } +} diff --git a/internal/model/user_table_model_gen.go b/internal/model/user_table_model_gen.go new file mode 100755 index 0000000..80a8b5f --- /dev/null +++ b/internal/model/user_table_model_gen.go @@ -0,0 +1,159 @@ +// Code generated by goctl. DO NOT EDIT. + +package model + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "github.com/zeromicro/go-zero/core/stores/builder" + "github.com/zeromicro/go-zero/core/stores/cache" + "github.com/zeromicro/go-zero/core/stores/sqlc" + "github.com/zeromicro/go-zero/core/stores/sqlx" + "github.com/zeromicro/go-zero/core/stringx" +) + +var ( + userTableFieldNames = builder.RawFieldNames(&UserTable{}) + userTableRows = strings.Join(userTableFieldNames, ",") + userTableRowsExpectAutoSet = strings.Join(stringx.Remove(userTableFieldNames, "`id`"), ",") + userTableRowsWithPlaceHolder = strings.Join(stringx.Remove(userTableFieldNames, "`id`"), "=?,") + "=?" + + cacheUserTableIdPrefix = "cache:userTable:id:" + cacheUserTableUidPrefix = "cache:userTable:uid:" +) + +type ( + userTableModel interface { + Insert(ctx context.Context, data *UserTable) (sql.Result, error) + FindOne(ctx context.Context, id int64) (*UserTable, error) + FindOneByUid(ctx context.Context, uid string) (*UserTable, error) + Update(ctx context.Context, data *UserTable) error + Delete(ctx context.Context, id int64) error + } + + defaultUserTableModel struct { + sqlc.CachedConn + table string + } + + UserTable struct { + Id int64 `db:"id"` + VerifyType int64 `db:"verify_type"` // 驗證類型 0. 異常 1.信箱 2.手機 3. GA 4.不驗證 + AlarmType int64 `db:"alarm_type"` // 告警狀態 0. 異常 1. 正常(未告警) 2.系統告警中 + Status int64 `db:"status"` // 會員狀態 0. 異常 1. 尚未驗證 2. 啟用 3. 停權中 4. 信箱以驗證 5. 手機以驗證 6. GA 以驗證 + Uid string `db:"uid"` // 唯一辨識碼 + NickName string `db:"nick_name"` // 暱稱 + Language string `db:"language"` // 使用語言 + Currency string `db:"currency"` // 使用幣別 + Avatar string `db:"avatar"` // 會員頭像 + CreateTime int64 `db:"create_time"` + UpdateTime int64 `db:"update_time"` + } +) + +func newUserTableModel(conn sqlx.SqlConn, c cache.CacheConf, opts ...cache.Option) *defaultUserTableModel { + return &defaultUserTableModel{ + CachedConn: sqlc.NewConn(conn, c, opts...), + table: "`user_table`", + } +} + +func (m *defaultUserTableModel) withSession(session sqlx.Session) *defaultUserTableModel { + return &defaultUserTableModel{ + CachedConn: m.CachedConn.WithSession(session), + table: "`user_table`", + } +} + +func (m *defaultUserTableModel) Delete(ctx context.Context, id int64) error { + data, err := m.FindOne(ctx, id) + if err != nil { + return err + } + + userTableIdKey := fmt.Sprintf("%s%v", cacheUserTableIdPrefix, id) + userTableUidKey := fmt.Sprintf("%s%v", cacheUserTableUidPrefix, data.Uid) + _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("delete from %s where `id` = ?", m.table) + return conn.ExecCtx(ctx, query, id) + }, userTableIdKey, userTableUidKey) + return err +} + +func (m *defaultUserTableModel) FindOne(ctx context.Context, id int64) (*UserTable, error) { + userTableIdKey := fmt.Sprintf("%s%v", cacheUserTableIdPrefix, id) + var resp UserTable + err := m.QueryRowCtx(ctx, &resp, userTableIdKey, func(ctx context.Context, conn sqlx.SqlConn, v any) error { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userTableRows, m.table) + return conn.QueryRowCtx(ctx, v, query, id) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultUserTableModel) FindOneByUid(ctx context.Context, uid string) (*UserTable, error) { + userTableUidKey := fmt.Sprintf("%s%v", cacheUserTableUidPrefix, uid) + var resp UserTable + err := m.QueryRowIndexCtx(ctx, &resp, userTableUidKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) { + query := fmt.Sprintf("select %s from %s where `uid` = ? limit 1", userTableRows, m.table) + if err := conn.QueryRowCtx(ctx, &resp, query, uid); err != nil { + return nil, err + } + return resp.Id, nil + }, m.queryPrimary) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *defaultUserTableModel) Insert(ctx context.Context, data *UserTable) (sql.Result, error) { + userTableIdKey := fmt.Sprintf("%s%v", cacheUserTableIdPrefix, data.Id) + userTableUidKey := fmt.Sprintf("%s%v", cacheUserTableUidPrefix, data.Uid) + ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, userTableRowsExpectAutoSet) + return conn.ExecCtx(ctx, query, data.VerifyType, data.AlarmType, data.Status, data.Uid, data.NickName, data.Language, data.Currency, data.Avatar, data.CreateTime, data.UpdateTime) + }, userTableIdKey, userTableUidKey) + return ret, err +} + +func (m *defaultUserTableModel) Update(ctx context.Context, newData *UserTable) error { + data, err := m.FindOne(ctx, newData.Id) + if err != nil { + return err + } + + userTableIdKey := fmt.Sprintf("%s%v", cacheUserTableIdPrefix, data.Id) + userTableUidKey := fmt.Sprintf("%s%v", cacheUserTableUidPrefix, data.Uid) + _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { + query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userTableRowsWithPlaceHolder) + return conn.ExecCtx(ctx, query, newData.VerifyType, newData.AlarmType, newData.Status, newData.Uid, newData.NickName, newData.Language, newData.Currency, newData.Avatar, newData.CreateTime, newData.UpdateTime, newData.Id) + }, userTableIdKey, userTableUidKey) + return err +} + +func (m *defaultUserTableModel) formatPrimary(primary any) string { + return fmt.Sprintf("%s%v", cacheUserTableIdPrefix, primary) +} + +func (m *defaultUserTableModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary any) error { + query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userTableRows, m.table) + return conn.QueryRowCtx(ctx, v, query, primary) +} + +func (m *defaultUserTableModel) tableName() string { + return m.table +} diff --git a/internal/model/vars.go b/internal/model/vars.go new file mode 100644 index 0000000..69ca814 --- /dev/null +++ b/internal/model/vars.go @@ -0,0 +1,5 @@ +package model + +import "github.com/zeromicro/go-zero/core/stores/sqlx" + +var ErrNotFound = sqlx.ErrNotFound diff --git a/internal/svc/service_context.go b/internal/svc/service_context.go index 127530e..9146b2a 100644 --- a/internal/svc/service_context.go +++ b/internal/svc/service_context.go @@ -1,13 +1,56 @@ package svc -import "app-cloudep-member-server/internal/config" +import ( + "app-cloudep-member-server/internal/config" + "app-cloudep-member-server/internal/domain" + domainUC "app-cloudep-member-server/internal/domain/usecase" + "app-cloudep-member-server/internal/model" + mgo "app-cloudep-member-server/internal/model/mongo" + "app-cloudep-member-server/internal/usecase" + ers "code.30cm.net/digimon/library-go/errors" + vi "code.30cm.net/digimon/library-go/validator" + "fmt" + "github.com/zeromicro/go-zero/core/stores/redis" + "github.com/zeromicro/go-zero/core/stores/sqlx" +) type ServiceContext struct { - Config config.Config + Config config.Config + Validate vi.Validate + + AccountModel model.AccountModel + UserModel model.UserTableModel + AccountToUidModel model.AccountToUidModel + GenUIDUseCase domainUC.UIDGenerateUseCase + Redis redis.Redis } func NewServiceContext(c config.Config) *ServiceContext { + // 設置 + ers.Scope = domain.Scope + + sqlConn := sqlx.NewMysql(c.DB.DsnString) + newRedis, err := redis.NewRedis(c.RedisCluster, redis.Cluster()) + if err != nil { + panic(err) + } + mongo := mgo.NewAutoIdModel( + fmt.Sprintf( + "%s://%s:%s@%s:%s", c.Mongo.Schema, + c.Mongo.User, c.Mongo.Password, c.Mongo.Host, c.Mongo.Port), + c.Mongo.Database, + c.Mongo.Collection, + c.Cache) + return &ServiceContext{ - Config: c, + Config: c, + Validate: vi.MustValidator(vi.WithAccount("account")), + Redis: *newRedis, + UserModel: model.NewUserTableModel(sqlConn, c.Cache), + AccountToUidModel: model.NewAccountToUidModel(sqlConn, c.Cache), + AccountModel: model.NewAccountModel(sqlConn, c.Cache), + GenUIDUseCase: usecase.MustGenerateUseCase(usecase.GenerateUseCaseParam{ + GenerateUIDRepo: mongo, + }), } } diff --git a/internal/usecase/google_authenticator.go b/internal/usecase/google_authenticator.go new file mode 100644 index 0000000..aed2454 --- /dev/null +++ b/internal/usecase/google_authenticator.go @@ -0,0 +1 @@ +package usecase diff --git a/internal/usecase/uid_generate.go b/internal/usecase/uid_generate.go new file mode 100644 index 0000000..5f5458a --- /dev/null +++ b/internal/usecase/uid_generate.go @@ -0,0 +1,75 @@ +package usecase + +import ( + "app-cloudep-member-server/internal/domain" + "app-cloudep-member-server/internal/domain/usecase" + mgo "app-cloudep-member-server/internal/model/mongo" + "context" + "math" + "strconv" + "strings" +) + +type GenerateUseCaseParam struct { + GenerateUIDRepo mgo.AutoIdModel +} + +type GenerateUseCase struct { + generateUIDRepo mgo.AutoIdModel +} + +func MustGenerateUseCase(param GenerateUseCaseParam) usecase.UIDGenerateUseCase { + return &GenerateUseCase{ + generateUIDRepo: param.GenerateUIDRepo, + } +} + +// Generate 利用 mongo 創立全局唯一的 ark id +// 如果之後有效能問題,扛在同一個 mongo 資料庫,在改成雪花算法 +func (g GenerateUseCase) Generate(ctx context.Context) (string, error) { + var data mgo.AutoId + err := g.generateUIDRepo.Inc(ctx, &data) + if err != nil { + return "", err + } + uid := strconv.Itoa(int(domain.InitAutoId + data.Count)) + code, err := generateReferralCode(uid) + if err != nil { + return "", err + } + + return code, nil +} + +// generateReferralCode 從 UID 生成 referralCode +func generateReferralCode(uid string) (string, error) { + uidInt, err := strconv.ParseInt(uid, 10, 64) + if err != nil { + return "", err + } + + maxReferralUIDBoundary := int64(math.Pow(float64(len(domain.ConvertTable)), float64(domain.DefaultReferralCodeLen))) + if uidInt > maxReferralUIDBoundary { + return "", usecase.UIDOutOfRangeErrorCodeError("uid encode out of range") + } + + encoded := encodeToBase(uidInt, len(domain.ConvertTable), domain.DefaultReferralCodeLen) + + return encoded, nil +} + +func encodeToBase(num int64, base int, length int) string { + result := "" + for num > 0 { + index := num % int64(base) + result = domain.ConvertTable[index] + result + num /= int64(base) + } + + // makes sure the length fo result feats the input length + if len(result) < length { + result = strings.Repeat(domain.ConvertTable[0], length-len(result)) + result + } + + return result +}