Existe um método para gerar um UUID com a linguagem go

109

Eu tenho um código parecido com este:

u := make([]byte, 16)
_, err := rand.Read(u)
if err != nil {
    return
}

u[8] = (u[8] | 0x80) & 0xBF // what does this do?
u[6] = (u[6] | 0x40) & 0x4F // what does this do?

return hex.EncodeToString(u)

Ele retorna uma string com comprimento de 32, mas não acho que seja um UUID válido. Se for um UUID real, por que é um UUID e qual é o propósito do código que modifica o valor de u[8]e u[6].

Existe uma maneira melhor de gerar UUIDs?

hardPass
fonte
1
Esta resposta parece mais adequada agora.
ViKiG

Respostas:

32
u[8] = (u[8] | 0x80) & 0xBF // what's the purpose ?
u[6] = (u[6] | 0x40) & 0x4F // what's the purpose ?

Essas linhas prendem os valores dos bytes 6 e 8 a um intervalo específico. rand.Readretorna bytes aleatórios no intervalo 0-255, que nem todos são valores válidos para um UUID. Pelo que eu posso dizer, isso deve ser feito para todos os valores na fatia.

Se você estiver no Linux, você pode alternativamente chamar /usr/bin/uuidgen.

package main

import (
    "fmt"
    "log"
    "os/exec"
)

func main() {
    out, err := exec.Command("uuidgen").Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s", out)
}

O que produz:

$ go run uuid.go 
dc9076e9-2fda-4019-bd2c-900a8284b9c4
Jimt
fonte
23
Notavelmente, essa abordagem é lenta; em um MacBook Air 2012, essa estratégia pode produzir apenas 170 uuids / segundo.
Jay Taylor
12
E usando a biblioteca nu7hatch / gouuid, consegui gerar 172.488 uuids / segundo.
Jay Taylor
2
Boa explicação dos bytes u[6]e u[8].
chowey
3
No meu sistema (Ubuntu 15.10) também precisei executar a saída do comando por meio de strings.Trim (string (out)) para remover o caractere de nova linha, caso contrário, ele foi inserido como um final? caractere no sistema de arquivos.
gregtczap
39
Chamar um programa externo que pode ou não existir é uma maneira horrível de fazer essa tarefa bastante simples.
Timmmm
96

Você pode gerar UUIDs usando a biblioteca go-uuid . Isso pode ser instalado com:

go get github.com/nu7hatch/gouuid

Você pode gerar UUIDs aleatórios (versão 4) com:

import "github.com/nu7hatch/gouuid"

...

u, err := uuid.NewV4()

O UUIDtipo retornado é uma matriz de 16 bytes, para que você possa recuperar o valor binário facilmente. Ele também fornece a representação de string hexadecimal padrão por meio de seu String()método.

O código que você tem também parece que gerará um UUID de versão 4 válido: a manipulação bit a bit que você executa no final define os campos de versão e variante do UUID para identificá-lo corretamente como versão 4 . Isso é feito para distinguir UUIDs aleatórios daqueles gerados por outros algoritmos (por exemplo, UUIDs da versão 1 com base em seu endereço MAC e hora).

James Henstridge
fonte
2
@Flimzy para pessoas que não sabem o que estão fazendo, isso é provavelmente verdade. Introduzir dependências desnecessárias é sempre uma coisa ruim.
Erik Aigner,
31
@ErikAigner Já que são 50 linhas eu não preciso pensar, escrever e testar, vou levá-las, obrigado .. Tenho outras coisas para fazer, então reinventar a roda.
RickyA
3
Esta biblioteca parece não ser compatível com RFC4122: github.com/nu7hatch/gouuid/issues/28 (edição atualmente aberta em 01/02/2016)
Charles L.
1
@ErikAigner reinventar a roda também é meio desnecessário. Se uma biblioteca existe e funciona bem, por que se preocupar em fazer a sua própria, a não ser se você está fazendo para aprender como fazê-la.
Senhor,
4
@ErikAigner, acho isso ridículo. Ninguém reinventa coisas que já estão feitas, a menos que você possa fazer melhor ou precisar de algo específico para o seu programa, se você inspecionar o código e ver que ele funciona bem, por que se preocupar em fazer você mesmo - você não só perde tempo e custo de desenvolvimento, você também está potencialmente vão trazer bugs ou simplesmente implementações erradas se você não souber completamente o que está fazendo, essas bibliotecas geralmente são feitas de pessoas que sabem o que estão fazendo. Não é novato usar bibliotecas de terceiros, seu único novato apenas presume que funciona e não inspeciona o código primeiro.
Senhor,
70

A go-uuidbiblioteca NÃO é compatível com RFC4122. Os bits variantes não estão definidos corretamente. Houve várias tentativas de membros da comunidade para consertar isso, mas as solicitações pull para a correção não estão sendo aceitas.

Você pode gerar UUIDs usando a biblioteca Go uuid que reescrevi com base na go-uuidbiblioteca. Existem várias correções e melhorias. Isso pode ser instalado com:

go get github.com/twinj/uuid

Você pode gerar UUIDs aleatórios (versão 4) com:

import "github.com/twinj/uuid"

u := uuid.NewV4()

O tipo de UUID retornado é uma interface e o tipo subjacente é uma matriz.

A biblioteca também gera UUIDs v1 e gera corretamente UUIDs v3 e 5. Existem vários novos métodos para ajudar na impressão e formatação e também novos métodos gerais para criar UUIDs com base nos dados existentes.

twinj
fonte
4
Eu gosto deste pacote. Eu o adotei oficialmente para todas as minhas aplicações. Descobri que o pacote nu7hatch não era compatível com RFC4122.
Richard Eng
+1 Acordado, as atualizações e extensões de impressão / formatação já incluídas.
eduncan911
4
Falta isenção de responsabilidade? : p
chakrit de
3
O que é a biblioteca "abaixo"? Você deve evitar usar o acima e o abaixo no SO, pois isso pode mudar rapidamente.
Stephan Dollberg
Há também outro equivalente, satori / go.uuid . Não tentei ainda, mas vou usá-lo como um substituto do projeto morto nu7hatch ...
shadyyx
52

"crypto / rand" é um pacote multiplataforma para geração aleatória de bytes

package main

import (
    "crypto/rand"
    "fmt"
)

// Note - NOT RFC4122 compliant
func pseudo_uuid() (uuid string) {

    b := make([]byte, 16)
    _, err := rand.Read(b)
    if err != nil {
        fmt.Println("Error: ", err)
        return
    }

    uuid = fmt.Sprintf("%X-%X-%X-%X-%X", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])

    return
}
Ken Cloud
fonte
3
pseudo_uuidporque está faltando os identificadores não aleatórios, como endereço MAC e tudo o mais especificado pela RFC4122? Portanto, é realmente mais aleatório.
Xeoncross de
2
boa resposta; Eu o expandi em stackoverflow.com/a/48134820/1122270 , e acho que muitas pessoas realmente não precisam usar UUIDs especificamente (nem o sha1 / sha256 que pensei que deveria usar para meu próprio problema de id), mas simplesmente deseja algo aleatório e único, e sua amostra fornece um bom começo para uma solução
cnst
Obrigado! Bastante simples
Karl Pokus,
1. Isso não está de acordo com nenhum padrão 2. Usar apenas %xtem problemas com valores de byte menores que 128, você precisa aplicar preenchimento, ou seja, %04xpara um par de bytes
Ja͢ck
38

Há uma implementação oficial do Google: https://github.com/google/uuid

Gerar um UUID versão 4 funciona assim:

package main

import (
    "fmt"
    "github.com/google/uuid"
)

func main() {
    id := uuid.New()
    fmt.Println(id.String())
}

Experimente aqui: https://play.golang.org/p/6YPi1djUMj9

shutefan
fonte
1
O godoc recomenda o uso New()e é equivalente auuid.Must(uuid.NewRandom())
Jim
@Jim: você está certo! Eu atualizei minha resposta de acordo.
shutefan de
Observe que New () pode ser "fatal" (o que é normal em alguns casos). Nos casos em que você não deseja que seu programa seja fatal, use apenas uuid.NewRandom () - que retorna um UUID e um erro.
Tomer
@Tomer: verdadeiro! Embora eu me pergunte em quais circunstâncias isso realmente aconteceria. Esta é a parte relevante do código: github.com/google/uuid/blob/… Por padrão, o leitor é um rand.Reader. Não tenho certeza se isso retornaria um erro ou se isso só pode acontecer com um leitor personalizado ...
shutefan
1
Olá @ shutefan - concordo que pode ser raro. rand.Reader chama funções do Kernel ( golang.org/src/crypto/rand/rand.go ). Isso pode falhar em certos cenários.
Tomer
12

Da postagem de Russ Cox :

Não há biblioteca oficial. Ignorando a verificação de erros, parece que funcionaria bem:

f, _ := os.Open("/dev/urandom")
b := make([]byte, 16)
f.Read(b)
f.Close()
uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])

Nota: Na versão original, pré Go 1, a primeira linha era:

f, _ := os.Open("/dev/urandom", os.O_RDONLY, 0)

Aqui, ele compila e executa, /dev/urandomretorna apenas todos os zeros no playground. Deve funcionar bem localmente.

No mesmo tópico existem alguns outros métodos / referências / pacotes encontrados.

zzzz
fonte
12
No entanto, isso não gerará um UUID válido: os UUIDs da versão 4 (o tipo baseado em dados aleatórios) requerem que alguns bits sejam definidos de uma determinada maneira para evitar conflito com os formatos UUID não aleatórios.
James Henstridge
4
Melhor usar import "crypto/rand"na minha opinião, mas 1 para uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]). Combinado com o código do OP, funciona muito bem.
chowey
2
Usando o pacote crypto / rand: play.golang.org/p/7JJDx4GL77 . O código do zzzz faz o que crypt / rand faz, exceto que também cobre plataformas que não suportam / dev / urandom (Windows).
Desenhou em
Deve-se observar que isso é específico da plataforma
Dan Esparza
2
@Matt: o problema é que os outros formatos UUID obtêm sua exclusividade delegando a alguma outra autoridade (por exemplo, que seu endereço MAC Ethernet é exclusivo) e, em seguida, combinando-o com outra coisa (por exemplo, o tempo mais um contador). Se você produzir um UUID aleatório que não está formatado corretamente como um V4, você estará enfraquecendo o sistema.
James Henstridge,
8

Como parte da especificação do uuid, se você gerar um uuid aleatoriamente, ele deverá conter um "4" como o 13º caractere e um "8", "9", "a" ou "b" no 17º ( fonte ).

// this makes sure that the 13th character is "4"
u[6] = (u[6] | 0x40) & 0x4F
// this makes sure that the 17th is "8", "9", "a", or "b"
u[8] = (u[8] | 0x80) & 0xBF 
Eric Chiang
fonte
4

O pacote gorand tem um método UUID que retorna um UUID Versão 4 (gerado aleatoriamente) em sua representação de string canônica ("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") e é compatível com RFC 4122.

Ele também usa o pacote crypto / rand para garantir a geração mais criptograficamente segura de UUIDs em todas as plataformas suportadas pelo Go.

import "github.com/leonelquinteros/gorand"

func main() {
    uuid, err := gorand.UUID()
    if err != nil {
        panic(err.Error())
    }

    println(uuid)
} 
peiiion
fonte
4

No Linux, você pode ler em /proc/sys/kernel/random/uuid:

package main

import "io/ioutil"
import "fmt"

func main() {
    u, _ := ioutil.ReadFile("/proc/sys/kernel/random/uuid")
    fmt.Println(string(u))
}

Sem dependências externas!

$ go run uuid.go 
3ee995e3-0c96-4e30-ac1e-f7f04fd03e44
batido de alma
fonte
4
Decisão negativa porque depender diretamente de uma plataforma host em uma linguagem de programação usada para aplicativos multiplataforma é pior do que uma dependência externa.
Byebye
1
A linguagem de programação pode ser multiplataforma, mas é muito comum soluções específicas do Linux que nunca estarão em outra plataforma, portanto, é uma resposta válida da IMO.
ton
1

Para Windows, fiz recentemente o seguinte:

// +build windows

package main

import (
    "syscall"
    "unsafe"
)

var (
    modrpcrt4 = syscall.NewLazyDLL("rpcrt4.dll")
    procUuidCreate = modrpcrt4.NewProc("UuidCreate")
)

const (
    RPC_S_OK = 0
)

func NewUuid() ([]byte, error) {
    var uuid [16]byte
    rc, _, e := syscall.Syscall(procUuidCreate.Addr(), 1,
             uintptr(unsafe.Pointer(&uuid[0])), 0, 0)
    if int(rc) != RPC_S_OK {
        if e != 0 {
            return nil, error(e)
        } else {
            return nil, syscall.EINVAL
        }
    }
    return uuid[:], nil
}
Kostix
fonte
2
Decisão negativa porque depender diretamente de uma plataforma host em uma linguagem de programação usada para aplicativos multiplataforma é pior do que uma dependência externa.
Byebye
1
@ Byebye, eu me pergunto por que você se considera uma autoridade para decidir o que "é pior" (e o que não é) ao folhear todas as respostas dadas a esta pergunta e votar negativamente em todas as que são "dependentes do sistema"? Essas respostas foram dadas para a) alargar o horizonte de todas as escolhas possíveis eb) apresentar coletivamente um quadro completo. Portanto, pare de "brincar ASSIM" infantilmente e pense um pouco antes de agir.
kostix
Resposta curta. Escrevendo código sustentável. Sua resposta não pode ser transferida para uma plataforma diferente. Portanto, se o OP escolhesse mover seu aplicativo para outra plataforma, o aplicativo seria interrompido. Eu tive meu quinhão de pessoas que escreveram código dependente de plataforma onde é totalmente desnecessário e cria mais problemas do que vale a pena. Você não escreve código apenas para você. Você escreve o código para as pessoas que irão mantê-lo depois que você partir. É por isso que essa resposta não é apropriada. Não há razão para recorrer a ad hominems e me chamar de infantil.
Byebye
1
@ Byebye, eu exagerei, então, por favor, me desculpe pelo ataque. Não estou convencido de suas razões, ainda, mas supostamente é aquele caso "vamos concordar em discordar".
kostix
1

Esta biblioteca é nosso padrão para geração e análise de uuid:

https://github.com/pborman/uuid

James McGill
fonte
Observe que a própria biblioteca do Google ( github.com/google/uuid ) é parcialmente baseada em github.com/pborman/uuid , que, por sua vez, incorporou de volta algumas das alterações feitas pelo Google. No entanto, supostamente, se você deseja contribuir para qualquer um desses projetos, você precisa assinar (ou ter assinado) um Contrato de Licença de Contribuidor (CLA). Aparentemente, esse não era o caso em agosto de 2015, quando sua resposta foi adicionada; @pborman adicionou isso apenas em 16 de fevereiro de 2016 .
Gwyneth Llewelyn