มีวิธีการสร้าง UUID ด้วยภาษา go


109

ฉันมีรหัสที่มีลักษณะดังนี้:

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)

มันส่งคืนสตริงที่มีความยาว 32 แต่ฉันไม่คิดว่ามันเป็น UUID ที่ถูกต้อง หากเป็น UUID จริงเหตุใดจึงเป็น UUID และอะไรคือวัตถุประสงค์ของรหัสที่แก้ไขค่าของu[8]และu[6].

มีวิธีที่ดีกว่าในการสร้าง UUID หรือไม่?


1
คำตอบนี้ดูเหมือนจะเหมาะสมกว่าในขณะนี้
ViKiG

คำตอบ:


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

เส้นเหล่านี้จะยึดค่าของไบต์ 6 และ 8 ไว้ที่ช่วงเฉพาะ rand.Readส่งคืนไบต์แบบสุ่มในช่วง0-255ซึ่งไม่ใช่ค่าที่ถูกต้องทั้งหมดสำหรับ UUID เท่าที่ฉันบอกได้ควรทำสิ่งนี้สำหรับค่าทั้งหมดในชิ้นส่วน

หากคุณใช้ linux คุณสามารถโทร/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)
}

ซึ่งให้ผลตอบแทน:

$ go run uuid.go 
dc9076e9-2fda-4019-bd2c-900a8284b9c4

23
โดยเฉพาะอย่างยิ่งแนวทางนี้ช้า ใน MacBook Air ปี 2012 กลยุทธ์นี้สามารถผลิตได้เพียง 170 uuids / วินาที
Jay Taylor

12
และด้วยการใช้ไลบรารี nu7hatch / gouuid ฉันสามารถสร้าง 172,488 uuids / วินาที
Jay Taylor

2
คำอธิบายที่ดีของu[6]และu[8]ไบต์
chowey

3
ในระบบของฉัน (Ubuntu 15.10) ฉันต้องเรียกใช้เอาต์พุตคำสั่งผ่านสตริงด้วยเช่นกันตัด (สตริง (ออก)) เพื่อลบอักขระขึ้นบรรทัดใหม่มิฉะนั้นจะถูกป้อนเข้าเป็นส่วนท้าย? อักขระในระบบไฟล์
gregtczap

39
การเรียกโปรแกรมภายนอกซึ่งอาจมีหรือไม่มีอยู่เป็นวิธีที่น่ากลัวในการทำงานนี้ค่อนข้างง่าย
Timmmm

96

คุณสามารถสร้าง UUID โดยใช้ไลบรารีgo-uuid สามารถติดตั้งได้ด้วย:

go get github.com/nu7hatch/gouuid

คุณสามารถสร้าง UUID แบบสุ่ม (เวอร์ชัน 4) ด้วย:

import "github.com/nu7hatch/gouuid"

...

u, err := uuid.NewV4()

UUIDประเภทที่ส่งคืนคืออาร์เรย์ 16 ไบต์ดังนั้นคุณจึงสามารถดึงค่าไบนารีได้อย่างง่ายดาย นอกจากนี้ยังมีการแสดงสตริงฐานสิบหกมาตรฐานด้วยString()วิธีการ

รหัสที่คุณยังดูเหมือนว่ามันยังจะสร้างที่ถูกต้อง 4 รุ่น UUID: การจัดการบิตที่คุณดำเนินการในตอนท้ายที่กำหนดรุ่นและตัวแปรเขตของ UUID อย่างถูกต้องระบุว่าเป็นรุ่นที่ 4 สิ่งนี้ทำเพื่อแยกความแตกต่างของ UUID แบบสุ่มจากที่สร้างผ่านอัลกอริทึมอื่น ๆ (เช่น UUID เวอร์ชัน 1 ตามที่อยู่ MAC และเวลาของคุณ)


2
@Flimzy สำหรับคนที่ไม่รู้ว่ากำลังทำอะไรอยู่นั้นน่าจะเป็นความจริงมากที่สุด การแนะนำการอ้างอิงที่ไม่จำเป็นเป็นสิ่งที่ไม่ดีเสมอ
Erik Aigner

31
@ErikAigner ตราบใดที่มันยาว 50 บรรทัดฉันไม่ต้องคิดเขียนและทดสอบฉันจะเอาไปขอบคุณ .. ฉันมีสิ่งอื่นที่ต้องทำแล้วสร้างวงล้อใหม่
RickyA

3
ห้องสมุดนี้ดูเหมือนว่าจะไม่สอดคล้องกับ RFC4122 จริง: github.com/nu7hatch/gouuid/issues/28 (ปัจจุบันเปิดให้บริการ ณ วันที่ 2/1/2559)
Charles L.

1
@ErikAigner การสร้างล้อใหม่ก็ไม่จำเป็นเช่นกัน ถ้าห้องสมุดมีอยู่และทำได้ดีทำไมต้องทำของคุณเองนอกจากคุณจะทำเพื่อเรียนรู้วิธีทำ
ท่าน

4
@ErikAigner ฉันพบว่ามันน่าขัน ไม่มีใครคิดค้นสิ่งใหม่ ๆ ที่ทำไปแล้วเว้นแต่คุณจะทำได้ดีกว่าหรือต้องการบางอย่างเฉพาะสำหรับโปรแกรมของคุณหากคุณตรวจสอบโค้ดและเห็นว่ามันทำได้ดีทำไมต้องทำด้วยตัวเอง - ไม่เพียง แต่เสียเวลาและค่าใช้จ่ายในการพัฒนาเท่านั้นคุณยัง อาจจะนำมาซึ่งจุดบกพร่องหรือการนำไปใช้งานที่ไม่ถูกต้องหากคุณไม่รู้ว่ากำลังทำอะไรอยู่โดยปกติแล้วไลบรารีเหล่านี้จะทำให้คนที่ไม่รู้ว่ากำลังทำอะไรอยู่ ไม่ใช่มือใหม่ที่จะใช้ไลบรารีของบุคคลที่สามเป็นมือใหม่เพียงคนเดียวที่คิดว่ามันใช้งานได้และไม่ได้ตรวจสอบโค้ดก่อน ..
เซอร์

70

go-uuidห้องสมุดไม่ RFC4122 สอดคล้อง ตั้งค่าบิตตัวแปรไม่ถูกต้อง มีความพยายามหลายครั้งโดยสมาชิกชุมชนในการแก้ไขปัญหานี้ แต่ไม่ยอมรับคำขอดึงสำหรับการแก้ไข

คุณสามารถสร้าง UUID โดยใช้ไลบรารี Go uuid ที่ฉันเขียนใหม่ตามgo-uuidไลบรารี มีการแก้ไขและปรับปรุงหลายประการ สามารถติดตั้งได้ด้วย:

go get github.com/twinj/uuid

คุณสามารถสร้าง UUID แบบสุ่ม (เวอร์ชัน 4) ด้วย:

import "github.com/twinj/uuid"

u := uuid.NewV4()

ประเภท UUID ที่ส่งคืนคืออินเทอร์เฟซและประเภทพื้นฐานคืออาร์เรย์

ไลบรารียังสร้าง v1 UUID และสร้าง v3 และ 5 UUIDs อย่างถูกต้อง มีวิธีการใหม่ ๆ มากมายที่จะช่วยในการพิมพ์และการจัดรูปแบบและวิธีการทั่วไปใหม่ ๆ ในการสร้าง UUID โดยอิงจากข้อมูลที่มีอยู่


4
ฉันชอบแพ็คเกจนี้ ฉันได้นำมาใช้อย่างเป็นทางการสำหรับแอปพลิเคชันทั้งหมดของฉัน ฉันพบว่าแพ็คเกจ nu7hatch ไม่สอดคล้องกับ RFC4122
Richard Eng

+1 ตกลงการอัปเดตและส่วนขยายการพิมพ์ / การจัดรูปแบบรวมอยู่แล้ว
eduncan911

4
ไม่มีข้อจำกัดความรับผิดชอบ? : p
chakrit

3
ห้องสมุด "ด้านล่าง" คืออะไร? คุณควรหลีกเลี่ยงการใช้ด้านบนและด้านล่างบน SO เนื่องจากสามารถเปลี่ยนแปลงได้ค่อนข้างเร็ว
Stephan Dollberg

นอกจากนี้ยังมี equivallent อีกริ / go.uuid ยังไม่ได้ลองใช้ แต่จะใช้แทนnu7hatch dead project ...
Shadyyx

52

"crypto / rand" คือ pkg ข้ามแพลตฟอร์มสำหรับการกำเนิดไบต์แบบสุ่ม

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
}

3
pseudo_uuidเนื่องจากไม่มีตัวระบุที่ไม่ใช่แบบสุ่มเช่นที่อยู่ MAC และสิ่งอื่นใดที่ RFC4122 ระบุ? มันเป็นแบบสุ่มมากกว่า
Xeoncross

2
คำตอบที่ดี; ฉันได้ขยายที่stackoverflow.com/a/48134820/1122270และฉันคิดว่าหลาย ๆ คนไม่จำเป็นต้องใช้ UUID โดยเฉพาะ (หรือ sha1 / sha256 ที่ฉันคิดว่าฉันต้องใช้สำหรับการสุ่มของฉันเอง - id ปัญหา) แต่เพียงแค่ต้องการบางสิ่งบางอย่างที่สุ่มและไม่เหมือนใครและตัวอย่างของคุณก็เป็นจุดเริ่มต้นที่ดีสำหรับการแก้ปัญหา
cnst

ขอบคุณ! ง่ายพอ
Karl Pokus

1. สิ่งนี้ไม่เป็นไปตามมาตรฐานใด ๆ 2. การใช้เพียงแค่%xมีปัญหากับค่าไบต์ที่น้อยกว่า 128 คุณต้องใช้ช่องว่างภายในเช่น%04xสำหรับคู่ไบต์
Ja͢ck

38

มีการนำไปใช้อย่างเป็นทางการโดย Google: https://github.com/google/uuid

การสร้าง UUID เวอร์ชัน 4 จะทำงานดังนี้:

package main

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

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

ลองดูที่นี่: https://play.golang.org/p/6YPi1djUMj9


1
godocแนะนำให้ใช้New()และมันจะเทียบเท่ากับuuid.Must(uuid.NewRandom())
จิม

@ จิม: ถูกแล้ว! ฉันอัปเดตคำตอบตามนั้น
shutefan

โปรดทราบว่า New () อาจ "ถึงแก่ชีวิต" (ซึ่งก็ใช้ได้ในบางกรณี) ในกรณีที่คุณไม่ต้องการให้โปรแกรมของคุณเสียชีวิตเพียงแค่ใช้ uuid.NewRandom () - ซึ่งส่งคืน UUID และข้อผิดพลาด
Tomer

@Tomer: จริง! แม้ว่าฉันจะสงสัยว่าเหตุการณ์นี้จะเกิดขึ้นได้อย่างไร นี่คือส่วนที่เกี่ยวข้องของรหัส: github.com/google/uuid/blob/…โดยค่าเริ่มต้นผู้อ่านคือ a rand.Reader. ฉันไม่แน่ใจว่าจะส่งคืนข้อผิดพลาดหรือไม่หรือว่าสิ่งนี้สามารถเกิดขึ้นได้กับ Reader ที่กำหนดเองเท่านั้น ...
shutefan

1
สวัสดี @shutefan - ฉันยอมรับว่ามันอาจจะหายาก Rand.Reader เรียกใช้ฟังก์ชันเคอร์เนล ( golang.org/src/crypto/rand/rand.go ) สิ่งเหล่านี้อาจล้มเหลวในบางสถานการณ์
Tomer

24

gofrs / uuidจะเปลี่ยนสำหรับริ / go.uuidซึ่งเป็นแพคเกจ UUID ติดดาวมากที่สุดสำหรับการไป รองรับ UUID เวอร์ชัน 1-5 และเป็นไปตามข้อกำหนด RFC 4122 และ DCE 1.1

import "github.com/gofrs/uuid"

// Create a Version 4 UUID, panicking on error
u := uuid.Must(uuid.NewV4())

12

จากโพสต์ของ Russ Cox :

ไม่มีห้องสมุดอย่างเป็นทางการ การละเว้นการตรวจสอบข้อผิดพลาดดูเหมือนว่าจะใช้ได้ดี:

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:])

หมายเหตุ: ในเวอร์ชันดั้งเดิมก่อน Go 1 เวอร์ชันแรกคือ:

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

ที่นี่รวบรวมและดำเนินการ/dev/urandomส่งกลับเฉพาะศูนย์ทั้งหมดในสนามเด็กเล่น ควรทำงานได้ดีในพื้นที่

ในเธรดเดียวกันมีวิธีการ / การอ้างอิง / แพ็คเกจอื่น ๆ ที่พบ


12
สิ่งนี้จะไม่สร้าง UUID ที่ถูกต้องแม้ว่า UUID เวอร์ชัน 4 (ประเภทที่ขึ้นอยู่กับข้อมูลแบบสุ่ม) จำเป็นต้องมีการตั้งค่าบิตเล็กน้อยในลักษณะที่แน่นอนเพื่อหลีกเลี่ยงความขัดแย้งกับรูปแบบ UUID ที่ไม่ใช่แบบสุ่ม
James Henstridge

4
ดีกว่าที่จะใช้import "crypto/rand"ในความคิดของฉัน แต่ +1 uuid := fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])สำหรับ รวมกับรหัสของ OP และใช้งานได้ดี
chowey

2
โดยใช้การเข้ารหัสลับแพคเกจ / แรนด์: play.golang.org/p/7JJDx4GL77 โค้ดของ zzzz ทำในสิ่งที่ crypt / rand ทำยกเว้นว่าจะครอบคลุมแพลตฟอร์มที่ไม่รองรับ / dev / urandom (Windows)
Drew

ควรสังเกตว่านี่เป็นแพลตฟอร์มเฉพาะ
Dan Esparza

2
@Matt: ปัญหาคือรูปแบบ UUID อื่น ๆ ได้รับความเป็นเอกลักษณ์โดยมอบหมายให้หน่วยงานอื่น ๆ (เช่นที่อยู่ MAC อีเทอร์เน็ตของคุณไม่ซ้ำกัน) จากนั้นรวมกับสิ่งอื่น (เช่นเวลาบวกตัวนับ) หากคุณสร้าง UUID แบบสุ่มที่มีรูปแบบไม่ถูกต้องเป็น V4 แสดงว่าคุณกำลังทำให้ระบบอ่อนแอลง
James Henstridge

8

ในฐานะที่เป็นส่วนหนึ่งของข้อมูลจำเพาะ uuid หากคุณสร้าง uuid จากการสุ่มต้องมี "4" เป็นอักขระที่ 13 และ "8", "9", "a" หรือ "b" ในลำดับที่ 17 ( แหล่งที่มา )

// 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 

4

gorandแพคเกจมีวิธี UUID ที่ส่งกลับรุ่นที่ 4 (ที่สร้างแบบสุ่ม) UUID ในการเป็นตัวแทนสตริงที่ยอมรับ ( "XXXXXXXXXXXX-XXXXXXXX-XXXXXXXXXXXX") และมัน RFC 4122 ที่สอดคล้องกับ

นอกจากนี้ยังใช้แพ็คเกจการเข้ารหัสลับ / แรนด์เพื่อให้แน่ใจว่าการสร้าง UUID ที่ปลอดภัยที่สุดในทุกแพลตฟอร์มที่ Go รองรับ

import "github.com/leonelquinteros/gorand"

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

    println(uuid)
} 

4

บน Linux คุณสามารถอ่านได้จาก/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))
}

ไม่มีการพึ่งพาภายนอก!

$ go run uuid.go 
3ee995e3-0c96-4e30-ac1e-f7f04fd03e44

4
ลดลงเนื่องจากขึ้นอยู่กับโฮสต์แพลตฟอร์มโดยตรงในภาษาโปรแกรมที่ใช้สำหรับแอปพลิเคชันหลายแพลตฟอร์มนั้นแย่กว่าการพึ่งพาภายนอก
Byebye

1
ภาษาการเขียนโปรแกรมสามารถเป็นได้หลายแพลตฟอร์ม แต่เป็นโซลูชันทั่วไปสำหรับ Linux ที่ไม่เคยมีอยู่ในแพลตฟอร์มอื่นดังนั้นจึงเป็น IMO ตอบสนองที่ถูกต้อง
ตัน

1

สำหรับ Windows ฉันเพิ่งทำสิ่งนี้:

// +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
}

2
ลดลงเนื่องจากขึ้นอยู่กับโฮสต์แพลตฟอร์มโดยตรงในภาษาโปรแกรมที่ใช้สำหรับแอปพลิเคชันหลายแพลตฟอร์มนั้นแย่กว่าการพึ่งพาภายนอก
Byebye

1
@ คุณบายฉันสงสัยว่าทำไมคุณถึงคิดว่าตัวเองมีอำนาจในการตัดสินใจว่าอะไร "แย่กว่า" (และอะไรที่ไม่ใช่) เพื่ออ่านคำตอบทั้งหมดที่ให้กับคำถามนี้และลงคะแนนทั้งหมดที่ "ขึ้นอยู่กับระบบ"? คำตอบเหล่านี้ได้รับไป) เปิดหูเปิดตาของตัวเลือกที่เป็นไปได้ทั้งหมดและ b) รวมนำเสนอภาพที่สมบูรณ์ ดังนั้นโปรดหยุด "เล่น SO" แบบเด็ก ๆ และคิดก่อนลงมือทำ
kostix

คำตอบสั้น ๆ การเขียนโค้ดที่บำรุงรักษาได้ คำตอบของคุณไม่สามารถย้ายไปยังแพลตฟอร์มอื่นได้ ดังนั้นหาก OP เลือกที่จะย้ายแอปพลิเคชันไปยังแพลตฟอร์มอื่นแอปพลิเคชันจะหยุดทำงาน ฉันมีส่วนแบ่งที่ยุติธรรมเกี่ยวกับคนที่เขียนโค้ดที่ขึ้นกับแพลตฟอร์มโดยที่มันไม่จำเป็นโดยสิ้นเชิงและมันสร้างปัญหามากกว่านั้นก็คุ้มค่า คุณไม่ได้เขียนโค้ดเพื่อตัวคุณเอง คุณเขียนโค้ดสำหรับคนที่จะดูแลมันหลังจากที่คุณไม่อยู่ นั่นเป็นเหตุผลที่คำตอบนี้ไม่เหมาะสม ไม่มีเหตุผลที่จะหันไปใช้โฆษณา hominems และเรียกฉันว่าเด็ก
Byebye

1
@ บายบายฉันแสดงปฏิกิริยามากเกินไปดังนั้นโปรดขอโทษสำหรับการโจมตี ฉันยังไม่มั่นใจในเหตุผลของคุณ แต่ก็น่าจะเป็นกรณีที่ "ตกลงที่จะไม่เห็นด้วย"
kostix

1

ไลบรารีนี้เป็นมาตรฐานสำหรับการสร้าง uuid และการแยกวิเคราะห์:

https://github.com/pborman/uuid


โปรดทราบว่าไลบรารีของ Google ( github.com/google/uuid ) บางส่วนอิงตามgithub.com/pborman/uuidซึ่งในทางกลับกันได้รวมการเปลี่ยนแปลงบางอย่างที่ Google ทำไว้ อย่างไรก็ตามหากคุณต้องการมีส่วนร่วมในโครงการใดโครงการหนึ่งเหล่านี้คุณต้องลงนาม (หรือลงนาม) ข้อตกลงใบอนุญาตผู้สนับสนุน (CLA) เห็นได้ชัดว่านี่ไม่ใช่กรณีในเดือนสิงหาคม 2015 เมื่อมีการเพิ่มคำตอบของคุณ @pborman ได้เพิ่มเมื่อวันที่16 ก.พ. 2559เท่านั้น
Gwyneth Llewelyn
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.