package member import ( bizDomain "biz-member-gateway/internal/domain" "code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/member" memberUC "code.30cm.net/digimon/app-cloudep-member-server/pkg/domain/usecase" tokenUC "code.30cm.net/digimon/app-cloudep-permission-server/pkg/domain/usecase" "time" "code.30cm.net/digimon/library-go/errs" "code.30cm.net/digimon/library-go/errs/code" "context" "github.com/golang/protobuf/proto" "biz-member-gateway/internal/svc" "biz-member-gateway/internal/types" memberProto "code.30cm.net/digimon/proto-all/pkg/member" permissionProto "code.30cm.net/digimon/proto-all/pkg/permission" "github.com/zeromicro/go-zero/core/logx" ) type AccountCreateLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } // NewAccountCreateLogic 創建新會員 func NewAccountCreateLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AccountCreateLogic { return &AccountCreateLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } // AccountCreate 建立新帳號 -> 業務邏輯 func (l *AccountCreateLogic) AccountCreate(req *types.CreateAccountRequest) (resp *types.LoginTokenResp, err error) { platform := member.GetPlatformByPlatformCode(req.Platform) if platform == member.PlatformNone { // http 400 return nil, errs.InvalidFormat("platform not support") } // Step 1: 根據平台構建相關數據 createAccountReq, bindingUserReq, createUserInfoRequest, err := l.buildAccountRequests(req, platform) if err != nil { // 裡面有錯從 Grpc 已經轉換完成 return nil, err } // Step 2: 建立帳號 _, err = l.svcCtx.MemberRPC.CreateUserAccount(l.ctx, &memberProto.CreateLoginUserReq{ LoginId: createAccountReq.LoginID, Token: createAccountReq.Token, Platform: createAccountReq.Platform.ToInt64(), }) if err != nil { return nil, err } // Step 3: 綁定帳號並獲取 UID account, err := l.svcCtx.MemberRPC.BindAccount(l.ctx, &memberProto.BindingUserReq{ LoginId: bindingUserReq.LoginID, Type: int64(bindingUserReq.Type), }) if err != nil { return nil, err } // Step 4: 更新使用者資訊 _, err = l.svcCtx.MemberRPC.BindUserInfo(l.ctx, &memberProto.CreateUserInfoReq{ Uid: account.Uid, AlarmType: memberProto.AlarmType(createUserInfoRequest.AlarmCategory), Status: memberProto.MemberStatus(createUserInfoRequest.UserStatus), Language: createUserInfoRequest.PreferredLanguage, Currency: createUserInfoRequest.Currency, Avatar: createUserInfoRequest.AvatarURL, NickName: createUserInfoRequest.Nickname, FullName: createUserInfoRequest.FullName, Gender: createUserInfoRequest.GenderCode, Birthdate: createUserInfoRequest.Birthdate, PhoneNumber: createUserInfoRequest.PhoneNumber, Email: createUserInfoRequest.Email, Address: createUserInfoRequest.Address, }) if err != nil { return nil, err } // TODO 綁定角色 // Step 5: 生成 Token t, err := l.generateToken(req, account.GetUid()) if err != nil { return nil, err } return &types.LoginTokenResp{ UID: account.GetUid(), AccessToken: t.AccessToken, RefreshToken: t.RefreshToken, TokenType: bizDomain.TokenTypeBearer, }, nil } // 構建不同平台的請求數據 func (l *AccountCreateLogic) buildAccountRequests(req *types.CreateAccountRequest, platform member.Platform) ( memberUC.CreateLoginUserRequest, memberUC.BindingUser, memberUC.CreateUserInfoRequest, error) { var createAccountReq memberUC.CreateLoginUserRequest var bindingUserReq memberUC.BindingUser var createUserInfoRequest memberUC.CreateUserInfoRequest switch platform { case member.Digimon: // 驗證 Token 是否一致 if req.Token != req.TokenCheck { return createAccountReq, bindingUserReq, createUserInfoRequest, errs.NewError( code.CloudEPMember, code.InvalidRange, bizDomain.TokenNotTheSameAPIErrorCode, "failed to verify check token", ) } createAccountReq = memberUC.CreateLoginUserRequest{ LoginID: req.Account, Token: req.Token, Platform: platform, } bindingUserReq = memberUC.BindingUser{ LoginID: req.Account, Type: member.GetAccountTypeByCode(req.AccountType), } createUserInfoRequest = memberUC.CreateUserInfoRequest{ AlarmCategory: member.AlarmNoAlert, UserStatus: member.AccountStatusUnverified, PreferredLanguage: bizDomain.DefaultLang, Currency: bizDomain.DefaultCurrency, } case member.Google: googleToken, err := l.svcCtx.MemberRPC.VerifyGoogleAuthResult(l.ctx, &memberProto.VerifyAuthResultReq{ Account: proto.String(req.Account), Token: req.Token, }) if err != nil { return createAccountReq, bindingUserReq, createUserInfoRequest, err } createAccountReq = memberUC.CreateLoginUserRequest{ LoginID: *googleToken.Email, Token: "", Platform: platform, } bindingUserReq = memberUC.BindingUser{ LoginID: *googleToken.Email, Type: member.GetAccountTypeByCode(req.AccountType), } createUserInfoRequest = memberUC.CreateUserInfoRequest{ AvatarURL: googleToken.Picture, FullName: googleToken.Name, Nickname: googleToken.Name, Email: googleToken.Email, AlarmCategory: member.AlarmNoAlert, UserStatus: member.AccountStatusActive, PreferredLanguage: bizDomain.DefaultLang, Currency: bizDomain.DefaultCurrency, } case member.Line: // 用 code 換取 line access token lineAccessToken, err := l.svcCtx.MemberRPC.LineCodeToAccessToken(l.ctx, &memberProto.LineGetTokenReq{ Code: req.Account, }) if err != nil { return createAccountReq, bindingUserReq, createUserInfoRequest, err } // 用 access token 換取 line 使用者資料 userInfo, err := l.svcCtx.MemberRPC.LineGetProfileByAccessToken(l.ctx, &memberProto.LineGetUserInfoReq{ Token: lineAccessToken.Token, }) if err != nil { return createAccountReq, bindingUserReq, createUserInfoRequest, err } createAccountReq = memberUC.CreateLoginUserRequest{ LoginID: userInfo.UserId, Token: "", Platform: platform, } bindingUserReq = memberUC.BindingUser{ LoginID: userInfo.UserId, Type: member.GetAccountTypeByCode(req.AccountType), } createUserInfoRequest = memberUC.CreateUserInfoRequest{ AvatarURL: proto.String(userInfo.PictureUrl), FullName: proto.String(userInfo.DisplayName), Nickname: proto.String(userInfo.DisplayName), AlarmCategory: member.AlarmNoAlert, UserStatus: member.AccountStatusActive, PreferredLanguage: bizDomain.DefaultLang, Currency: bizDomain.DefaultCurrency, } case member.PlatformNone: default: return createAccountReq, bindingUserReq, createUserInfoRequest, errs.InvalidFormat("invalid platform") } return createAccountReq, bindingUserReq, createUserInfoRequest, nil } // 生成 Token 這裡是註冊的,所以角色是固定的,不能讓平常註冊的使用者選擇角色 func (l *AccountCreateLogic) generateToken(req *types.CreateAccountRequest, uid string) (tokenUC.AccessTokenResponse, error) { credentials := "client_credentials" // 生成預設的Role -> 所有邏輯上的其他角色,應該都要從 user 開始,後續再通過admin 做更改 // 第一個 admin 應該是直接插入資料庫的 t, err := l.svcCtx.TokenRPC.NewToken(l.ctx, &permissionProto.AuthorizationReq{ GrantType: credentials, DeviceId: req.DeviceID, Scope: bizDomain.DefaultRole, IsRefreshToken: true, Account: req.Account, Uid: uid, Expires: proto.Int64(time.Now().UTC().Add(l.svcCtx.Config.Token.Expired).UnixNano()), // 指定到期的時間 RefreshExpire: proto.Int64(time.Now().UTC().Add(l.svcCtx.Config.Token.RefreshExpired).UnixNano()), // 指定到期的時間 Data: map[string]string{}, Role: bizDomain.DefaultRole, }) if err != nil { return tokenUC.AccessTokenResponse{}, errs.FromGRPCError(err) } return tokenUC.AccessTokenResponse{ AccessToken: t.AccessToken, ExpiresIn: t.ExpiresIn, RefreshToken: t.RefreshToken, }, nil }