Каков наилучший способ получить полудлинный ключ уникального идентификатора (непоследовательный) для объектов базы данных - PullRequest
18 голосов
/ 26 марта 2012

Я создаю веб-приложение, и мне бы хотелось, чтобы моя схема URL выглядела примерно так:

someurl.com/object/FJ1341lj

В настоящее время я просто использую первичный ключ из своих объектов SQL Alchemy, но проблема в том, что я нехотите, чтобы URL были последовательными или низкими числами.Например, мои URL выглядят так:

someurl.com/object/1
someurl.com/object/2

Ответы [ 4 ]

22 голосов
/ 26 марта 2012

Кодирование целых чисел

Вы можете использовать обратимую кодировку для ваших целых чисел:

def int_str(val, keyspace):
    """ Turn a positive integer into a string. """
    assert val >= 0
    out = ""
    while val > 0:
        val, digit = divmod(val, len(keyspace))
        out += keyspace[digit]
    return out[::-1]

def str_int(val, keyspace):
    """ Turn a string into a positive integer. """
    out = 0
    for c in val:
        out = out * len(keyspace) + keyspace.index(c)
    return out

Код быстрого тестирования:

keyspace = "fw59eorpma2nvxb07liqt83_u6kgzs41-ycdjh" # Can be anything you like - this was just shuffled letters and numbers, but...
assert len(set(keyspace)) == len(keyspace) # each character must occur only once

def test(v):
    s = int_str(v, keyspace)
    w = str_int(s, keyspace)
    print "OK? %r -- int_str(%d) = %r; str_int(%r) = %d" % (v == w, v, s, s, w)

test(1064463423090)
test(4319193500)
test(495689346389)
test(2496486533)

выходы

OK? True -- int_str(1064463423090) = 'antmgabi'; str_int('antmgabi') = 1064463423090
OK? True -- int_str(4319193500) = 'w7q0hm-'; str_int('w7q0hm-') = 4319193500
OK? True -- int_str(495689346389) = 'ev_dpe_d'; str_int('ev_dpe_d') = 495689346389
OK? True -- int_str(2496486533) = '1q2t4w'; str_int('1q2t4w') = 2496486533

Запутывает их и делает их непостоянными

Чтобы сделать идентификаторы несмежными, можно, скажем, умножить исходное значение на какое-то произвольное значение, добавить случайную "chaff" в качестве отбрасываемых цифр - с простой проверкой модуля в моем примере:

def chaffify(val, chaff_size = 150, chaff_modulus = 7):
    """ Add chaff to the given positive integer.
    chaff_size defines how large the chaffing value is; the larger it is, the larger (and more unwieldy) the resulting value will be.
    chaff_modulus defines the modulus value for the chaff integer; the larger this is, the less chances there are for the chaff validation in dechaffify() to yield a false "okay".
    """
    chaff = random.randint(0, chaff_size / chaff_modulus) * chaff_modulus
    return val * chaff_size + chaff

def dechaffify(chaffy_val, chaff_size = 150, chaff_modulus = 7):
    """ Dechaffs the given chaffed value. The chaff_size and chaff_modulus parameters must be the same as given to chaffify() for the dechaffification to succeed.
    If the chaff value has been tampered with, then a ValueError will (probably - not necessarily) be raised. """
    val, chaff = divmod(chaffy_val, chaff_size)
    if chaff % chaff_modulus != 0:
        raise ValueError("Invalid chaff in value")
    return val

for x in xrange(1, 11):
    chaffed = chaffify(x)
    print x, chaffed, dechaffify(chaffed)

выходов (со случайностью):

1 262 1
2 440 2
3 576 3
4 684 4
5 841 5
6 977 6
7 1197 7
8 1326 8
9 1364 9
10 1528 10

РЕДАКТИРОВАТЬ: Если подумать, случайность мякины не может быть хорошей идеей, так как вы теряете каноничность каждого запутанного идентификатора - это лишает случайности, но все еще имеет проверку (изменение одной цифры вероятно, сделает недействительным целое число, если chaff_val достаточно большое).

def chaffify2(val, chaff_val = 87953):
    """ Add chaff to the given positive integer. """
    return val * chaff_val

def dechaffify2(chaffy_val, chaff_val = 87953):
    """ Dechaffs the given chaffed value. chaff_val must be the same as given to chaffify2(). If the value does not seem to be correctly chaffed, raises a ValueError. """
    val, chaff = divmod(chaffy_val, chaff_val)
    if chaff != 0:
        raise ValueError("Invalid chaff in value")
    return val

Собираем все вместе

document_id = random.randint(0, 1000000)
url_fragment = int_str(chaffify(document_id))
print "URL for document %d: http://example.com/%s" % (document_id, url_fragment)
request_id = dechaffify(str_int(url_fragment))
print "Requested: Document %d" % request_id

выходов (со случайностью)

URL for document 831274: http://example.com/w840pi
Requested: Document 831274
1 голос
/ 26 марта 2012

возможно немного дольше, чем хотелось бы.

Python 2.7.1+ (r271:86832, Apr 11 2011, 18:13:53) 
[GCC 4.5.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import uuid
>>> uuid.uuid4()
UUID('ba587488-2a96-4daa-b422-60300eb86155')
>>> str(uuid.uuid4())
'001f8565-6330-44a6-977a-1cca201aedcc'
>>> 

И если вы используете sqlalchemy, вы можете определить столбец id типа uuid, например, так:

from sqlalchemy import types
from sqlalchemy.databases.mysql import MSBinary
from sqlalchemy.schema import Column
import uuid


class UUID(types.TypeDecorator):
    impl = MSBinary
    def __init__(self):
        self.impl.length = 16
        types.TypeDecorator.__init__(self,length=self.impl.length)

    def process_bind_param(self,value,dialect=None):
        if value and isinstance(value,uuid.UUID):
            return value.bytes
        elif value and not isinstance(value,uuid.UUID):
            raise ValueError,'value %s is not a valid uuid.UUID' % value
        else:
            return None

    def process_result_value(self,value,dialect=None):
        if value:
            return uuid.UUID(bytes=value)
        else:
            return None

    def is_mutable(self):
        return False


id_column_name = "id"

def id_column():
    import uuid
    return Column(id_column_name,UUID(),primary_key=True,default=uuid.uuid4)

Если вы используете Django, ответ Преета, вероятно, более уместен, так как многие вещи django зависят от первичных ключей, которые являются целыми числами.

0 голосов
/ 26 марта 2012

Учитывая ваши требования, лучше всего было бы использовать itertools.combinsk примерно так

>>> urls=itertools.combinations(string.ascii_letters,6)
>>> 'someurl.com/object/'+''.join(x.next())
'someurl.com/object/abcdek'
>>> 'someurl.com/object/'+''.join(x.next())
'someurl.com/object/abcdel'
>>> 'someurl.com/object/'+''.join(x.next())
'someurl.com/object/abcdem'
0 голосов
/ 26 марта 2012

вы всегда можете взять хеш идентификатора, а затем представить результирующее число с основанием 62 (0-9, a-z, A-Z) основанием.

import string
import hashlib

def enc(val):
    chars = string.digits + string.letters
    num_chars = len(chars)
    r=''
    while val!= 0:
        r+=chars[val % num_chars]
        val/=num_chars
    return r

def fancy_id(i, hash_truncate=12):
    h = hashlib.sha1(str(i))
    return enc(int(h.hexdigest()[:hash_truncate], 16))

fancy_id(1) # 'XYY6dYFg'
fancy_id(2) # '6jxNvE961'

аналогично, существует функция декодирования. Вы должны сохранить этот сгенерированный идентификатор URL в вашем объекте. так что вы можете отобразить свой URL-адрес на объект.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...