2

我是 ndb 和 gae 的新手,在提出一个好的解决方案设置索引时遇到了问题。假设我们有一个这样的用户模型:

class User(ndb.Model):
    name = ndb.StringProperty()    
    email = ndb.StringProperty(required = True)    
    fb_id = ndb.StringProperty()

登录后,如果我要通过查询检查电子邮件地址,我相信这将非常缓慢且效率低下。可能它必须进行全表扫描。

q = User.query(User.email == EMAIL)
user = q.fetch(1)

如果用户模型以电子邮件作为密钥保存,我相信它会快得多。

user = user(id=EMAIL)
user.put()

这样我就可以更快地像这样检索它们(所以我相信)

key = ndb.Key('User', EMAIL) 
user = key.get()

到目前为止,如果我错了,请纠正我。但在实施此操作后,我意识到 facebook 用户有可能更改他们的电子邮件地址,这样在新的 oauth2.0 连接时,系统无法识别他们的新电子邮件,他们将被创建为新用户。因此,也许我应该使用不同的方法:

  • 使用 social-media-provider-id(所有提供商用户唯一)

  • provider-name(在极少数情况下,两个 twitter 和 facebook 用户共享相同的 provider-id)

但是为了实现这一点,我需要设置两个索引,我认为这是不可能的

那么我能做什么呢?我应该将两个字段连接为一个键和索引吗?

例如,新想法是:

class User(ndb.Model):
    name = ndb.StringProperty()    
    email = ndb.StringProperty(required = True)    
    provider_id = ndb.StringProperty()
    provider_type = ndb.StringProperty()

保存:

provider_id = 1234
provider_type = fb
user = user(id=provider_id + provider_type)
user.put()

恢复:

provider_id = 1234
provider_type = fb
key = ndb.Key('User', provider_id + provider_type) 
user = key.get()

这样我们就不再关心用户是否在他的社交媒体上更改了电子邮件地址。这个想法合理吗?

谢谢,

更新

到目前为止,蒂姆的解决方案听起来最干净,对我来说可能也是最快的。但是我遇到了一个问题。

class AuthProvider(polymodel.PolyModel):
    user_key = ndb.KeyProperty(kind=User)
    active = ndb.BooleanProperty(default=True)  
    date_created = ndb.DateTimeProperty(auto_now_add=True)

    @property
    def user(self):
        return self.user_key.get()

class FacebookLogin(AuthProvider):
    pass

View.py:在 facebook_callback 方法中

provider = ndb.Key('FacebookLogin', fb_id).get() 

# Problem is right here. provider is always None. Only if I used the PolyModel like this:
# ndb.Key('AuthProvider', fb_id).get()
#But this defeats the whole purpose of having different sub classes as different providers. 
#Maybe I am using the key handeling wrong?


if provider:
    user = provider.user
else:
    provider = FacebookLogin(id=fb_id)          
if not user:
        user = User()
        user_key = user.put()
        provider.user_key = user_key
        provider.put() 
return user
4

4 回答 4

3

可以允许更灵活模型的方法的一个细微变化是为 provider_id、provider_type 创建一个单独的实体,作为密钥或您提出的任何其他身份验证方案

然后,该实体持有实际用户详细信息的引用(键)。

然后你可以

  1. 直接 get() 获取身份验证详细信息,然后 get() 实际用户详细信息。
  2. 无需实际重写/重新键入用户详细信息即可更改身份验证详细信息
  3. 您可以为单个用户支持多个身份验证方案。

我将这种方法用于具有 > 2000 个用户的应用程序,大多数使用自定义身份验证方案(应用程序特定的用户 ID/密码)或谷歌帐户。

例如

class AuthLogin(ndb.Polymodel):
     user_key = ndb.KeyProperty(kind=User)
     status = ndb.StringProperty()  # maybe you need to disable a particular login with out deleting it.
     date_created = ndb.DatetimeProperty(auto_now_add=True)

     @property
     def user(self):
         return self.user_key.get()


class FacebookLogin(AuthLogin):
    # some additional facebook properties

class TwitterLogin(AuthLogin):
    # Some additional twitter specific properties

ETC...

通过使用 PolyModel 作为基类,您可以执行AuthLogin.query().filter(AuthLogin.user_key == user.key) 并获取为该用户定义的所有身份验证类型,因为它们都共享相同的基类 AuthLogin。您需要这个,否则您必须依次查询每种支持的身份验证类型,因为没有祖先就无法进行 kindless 查询,在这种情况下,我们不能将User用作祖先,因为我们无法执行简单的 get() 从登录 ID。

但是需要注意的是,AuthLogin 的所有子类将在“AuthLogin”键中共享相同的类型,因此您仍然需要将 auth_provider 和 auth_type 连接到键 id 中,以确保您拥有唯一的键。例如

dev~fish-and-lily> from google.appengine.ext.ndb.polymodel import PolyModel
dev~fish-and-lily> class X(PolyModel):
...    pass
... 
dev~fish-and-lily> class Y(X):
...    pass
... 
dev~fish-and-lily> class Z(X):
...    pass
... 
dev~fish-and-lily> y = Y(id="abc")
dev~fish-and-lily> y.put()
Key('X', 'abc')
dev~fish-and-lily> z = Z(id="abc")
dev~fish-and-lily> z.put()
Key('X', 'abc')
dev~fish-and-lily> y.key.get()
Z(key=Key('X', 'abc'), class_=[u'X', u'Z'])

dev~fish-and-lily> z.key.get()
Z(key=Key('X', 'abc'), class_=[u'X', u'Z'])

这是你遇到的问题。通过将提供程序类型添加为密钥的一部分,您现在可以获得不同的密钥。

dev~fish-and-lily> z = Z(id="Zabc")
dev~fish-and-lily> z.put()
Key('X', 'Zabc')
dev~fish-and-lily> y = Y(id="Yabc")
dev~fish-and-lily> y.put()
Key('X', 'Yabc')
dev~fish-and-lily> y.key.get()
Y(key=Key('X', 'Yabc'), class_=[u'X', u'Y'])
dev~fish-and-lily> z.key.get()
Z(key=Key('X', 'Zabc'), class_=[u'X', u'Z'])
dev~fish-and-lily> 

我不相信这对你来说是一个不方便的模型。

这一切有意义吗;-)

于 2013-07-03T00:58:49.797 回答
2

虽然@Greg 的答案似乎还可以,但我认为将外部类型/ID 关联为实体的键实际上是一个坏主意,因为这种解决方案不能很好地扩展。

  • 如果您想在某一时刻实现自己的用户名/密码怎么办?
  • 如果用户要删除他们的 Facebook 帐户怎么办?
  • 如果同一用户也想使用 Twitter 帐户登录怎么办?
  • 如果用户拥有多个 Facebook 帐户怎么办?

因此,将 type/id 作为键的想法看起来很弱。更好的解决方案是为每种类型设置一个字段来仅存储 id。例如facebook_idtwitter_idgoogle_id,然后查询这些字段以检索实际用户。这将在登录和注册过程中发生,因此并不经常发生。当然,如果同一用户使用不同的提供者登录,您将不得不添加一些逻辑来为已存在的用户添加另一个提供者或合并用户。

如果您想支持来自同一提供商的多个登录,最后一个解决方案仍然不起作用。为了实现这一点,您必须创建另一个模型,该模型将仅存储外部提供者/ID 并将它们与您的用户模型相关联。

作为第二个解决方案的示例,您可以检查我的gae-init项目,其中我将 3 个不同的提供程序存储在User模型中并在auth.py模块中处理它们。同样,此解决方案不能很好地与更多提供商一起扩展,并且不支持来自同一提供商的多个 ID。

于 2013-07-02T23:34:49.420 回答
1

如果要使用单个模型,可以执行以下操作:

class User(ndb.Model):
    name = ndb.StringProperty()
    email = ndb.StringProperty(required = True)
    providers = ndb.KeyProperty(repeated=True)

auser = User(id="auser", name="A user", email="auser@example.com")
auser.providers = [
    ndb.Key("ProviderName", "fb", "ProviderId", 123),
    ndb.Key("ProviderName", "tw", "ProviderId", 123)
]
auser.put()

要查询特定的 FB 登录,您只需执行以下操作:

fbkey = ndb.Key("ProviderName", "fb", "ProviderId", 123)
for entry in User.query(User.providers==fbkey):
    # Do something with the entry

由于ndb没有提供创建唯一约束的简单方法,您可以使用_pre_put_hook来确保它providers是唯一的。

于 2013-07-03T15:07:16.787 回答
1

将用户类型与他们的 ID 连接起来是明智的。

您可以通过不将类型和 ID 复制为属性来节省读取和写入成本 - 当您需要使用它们时,只需将 ID 拆分备份即可。'%s|%s' % (provider_type, provider_id)(例如,如果您在部件之间包含分隔符,这样做会更简单)

于 2013-07-02T19:34:27.447 回答