package logic import ( "context" "errors" "strconv" "strings" "lone-services/pkg/modelbase" "lone-services/pkg/utils" "lone-services/services/user/internal/config" "lone-services/services/user/internal/dao" "lone-services/services/user/internal/model" jsoniter "github.com/json-iterator/go" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "gorm.io/gorm" ) func clientAllowsGrant(allowedGrants, grantType string) bool { if allowedGrants == utils.StringEmpty { return false } var grants []string if err := jsoniter.UnmarshalFromString(allowedGrants, &grants); err != nil { return strings.Contains(allowedGrants, grantType) } for _, g := range grants { if g == grantType { return true } } return false } func loadEnabledClient(clientCode string) (*dao.Client, error) { var client dao.Client clientModel := model.ClientModel{}.Init() if err := clientModel.GetOne(modelbase.Params{ Eq: map[string]string{ "code": clientCode, "status": strconv.Itoa(int(dao.StatusEnabled)), }, }, &client); err != nil { return nil, err } if client.Id < utils.NumberOne { return nil, status.Error(codes.InvalidArgument, "端配置不存在或已禁用") } return &client, nil } func findCredential(credentialType, identifier, subjectType string) (*dao.UserCredential, error) { var cred dao.UserCredential credModel := model.UserCredentialModel{}.Init() if err := credModel.GetOne(modelbase.Params{ Eq: map[string]string{ "credential_type": credentialType, "identifier": identifier, "subject_type": subjectType, }, }, &cred); err != nil { return nil, err } if cred.Id < utils.NumberOne { return nil, nil } return &cred, nil } func userHasEnabledClient(userId int64, clientCode string) (bool, error) { var row dao.UserClient clientModel := model.UserClientModel{}.Init() if err := clientModel.GetOne(modelbase.Params{ Eq: map[string]string{ "user_id": strconv.FormatInt(userId, utils.NumberTen), "client_code": clientCode, "status": strconv.Itoa(int(dao.StatusEnabled)), }, }, &row); err != nil { return false, err } return row.Id >= utils.NumberOne, nil } func loadEnabledUser(userId int64) (*dao.UserRow, error) { var row dao.UserRow userModel := model.UserModel{}.Init() if err := userModel.GetOne(modelbase.Params{ Eq: map[string]string{ "id": strconv.FormatInt(userId, utils.NumberTen), "status": strconv.Itoa(int(dao.StatusEnabled)), }, }, &row); err != nil { return nil, err } if row.Id < utils.NumberOne { return nil, status.Error(codes.NotFound, utils.ErrorNotFund.Msg) } return &row, nil } func resolveOrCreateWechatUser(ctx context.Context, db *gorm.DB, openid, unionid, clientCode, subjectType string) (int64, error) { openCred, err := findCredential(dao.CredentialTypeOpenid, openid, subjectType) if err != nil { return utils.NumberZero, err } if openCred != nil { if openCred.Status == dao.StatusDisabled { return utils.NumberZero, status.Error(codes.FailedPrecondition, "登录凭证已禁用") } if err := ensureUserDetail(db, openCred.UserId); err != nil { return utils.NumberZero, err } if err := ensureUserClient(db, openCred.UserId, clientCode, utils.NumberZero); err != nil { return utils.NumberZero, err } return openCred.UserId, nil } var userId int64 if unionid != utils.StringEmpty { unionCred, uErr := findCredential(dao.CredentialTypeUnionid, unionid, subjectType) if uErr != nil { return utils.NumberZero, uErr } if unionCred != nil { if unionCred.Status == dao.StatusDisabled { return utils.NumberZero, status.Error(codes.FailedPrecondition, "登录凭证已禁用") } userId = unionCred.UserId } } txErr := db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if userId < utils.NumberOne { userModel := model.UserModel{}.Init() userModel.Base = userModel.Base.WithTX(tx) add := dao.UserCreate{Status: dao.StatusEnabled, Gender: dao.GenderUnknown} if err := userModel.Create(&add); err != nil { return err } userId = add.Id } if err := ensureUserDetail(tx, userId); err != nil { return err } if err := createWechatCredentials(tx, userId, openid, unionid, subjectType); err != nil { return err } return ensureUserClient(tx, userId, clientCode, utils.NumberZero) }) if txErr != nil { if st, ok := status.FromError(txErr); ok { return utils.NumberZero, st.Err() } return utils.NumberZero, txErr } return userId, nil } func createWechatCredentials(tx *gorm.DB, userId int64, openid, unionid, subjectType string) error { credModel := model.UserCredentialModel{}.Init() credModel.Base = credModel.Base.WithTX(tx) var existOpen dao.UserCredential if err := credModel.GetOne(modelbase.Params{ Eq: map[string]string{ "credential_type": dao.CredentialTypeOpenid, "identifier": openid, "subject_type": subjectType, }, }, &existOpen); err != nil { return err } if existOpen.Id < utils.NumberOne { if err := credModel.Create(&dao.UserCredential{ UserId: userId, CredentialType: dao.CredentialTypeOpenid, Identifier: openid, SubjectType: subjectType, ExtraJson: dao.ExtraJsonEmpty, Status: dao.StatusEnabled, }); err != nil { return err } } else if existOpen.UserId != userId { return status.Error(codes.AlreadyExists, "openid已被其他用户占用") } if unionid == utils.StringEmpty { return nil } var existUnion dao.UserCredential if err := credModel.GetOne(modelbase.Params{ Eq: map[string]string{ "credential_type": dao.CredentialTypeUnionid, "identifier": unionid, "subject_type": subjectType, }, }, &existUnion); err != nil { return err } if existUnion.Id < utils.NumberOne { return credModel.Create(&dao.UserCredential{ UserId: userId, CredentialType: dao.CredentialTypeUnionid, Identifier: unionid, SubjectType: subjectType, ExtraJson: dao.ExtraJsonEmpty, Status: dao.StatusEnabled, }) } if existUnion.UserId != userId { return status.Error(codes.AlreadyExists, "unionid 已被其他用户占用") } return nil } func loadUserClientBizId(userId int64, clientCode string) (int64, error) { var row dao.UserClient err := model.UserClientModel{}.Init().GetOne(modelbase.Params{ Eq: map[string]string{ "user_id": strconv.FormatInt(userId, utils.NumberTen), "client_code": clientCode, "status": strconv.Itoa(int(dao.StatusEnabled)), }, }, &row) if err != nil { return utils.NumberZero, err } return row.BizId, nil } func issueLoginToken(ctx context.Context, jwtAuth *config.JWTAuth, row *dao.UserRow, client *dao.Client) (*dao.Token, error) { if jwtAuth == nil || jwtAuth.Access == nil || jwtAuth.Refresh == nil { return nil, errors.New("jwt not initialized") } mobile, perr := utils.DecryptPhone(row.Mobile) if perr != nil { return nil, errors.New("decrypt phone error") } session := toSession(*row, mobile, client) if err := fillSessionBizIds(ctx, &session, row.Id, client); err != nil { return nil, err } // C 端多小程序共用同一 token:已登录则续期并更新会话;门店/销售仍发新 token if renewed, ok := tryRenewUserLogin(jwtAuth, session, client); ok { return renewed, nil } ret, err := buildJwtToken(jwtAuth, session) if err != nil { return nil, err } if st := setLogin(jwtAuth, ret.AccessToken, ret.RefreshToken, session, client); st.Code != utils.Ok.Code { return nil, errors.New(st.Msg) } return &ret, nil } func fillSessionBizIds(ctx context.Context, session *dao.UserLoginSession, userId int64, client *dao.Client) error { switch { case client.Type == dao.TypeSale: saleInfo, err := fetchSaleByUserId(ctx, utils.GetConfigString("services.sale"), userId) if err != nil { return err } if saleInfo != nil { session.BizId = saleInfo.Id } case client.Type == dao.TypeStore: bizId, err := loadUserClientBizId(userId, client.Code) if err != nil { return err } if bizId < utils.NumberOne { return nil } session.BizId = bizId storeInfo, err := fetchStoreById(ctx, utils.GetConfigString("services.store"), bizId) if err != nil { return err } if storeInfo != nil { session.BizId = storeInfo.Id } default: bizId, err := loadUserClientBizId(userId, client.Code) if err != nil { return err } if bizId > utils.NumberZero { session.BizId = bizId } } return nil }