Библиотека uuid для Go: полное руководство по генерации UUID
1. Что это и зачем нужна
UUID (Universally Unique Identifier) — это стандарт идентификации, используемый в разработке программного обеспечения для создания уникальных идентификаторов без необходимости централизованного координатора. В экосистеме Go библиотека github.com/google/uuid является де-факто стандартом для работы с UUID. Она предоставляет надежные и производительные инструменты для генерации, парсинга и сравнения UUID различных версий.
Зачем нужна эта библиотека? В современных распределенных системах, микросервисной архитектуре и базах данных часто требуется создавать уникальные идентификаторы, которые не будут конфликтовать между разными узлами системы. UUID решает эту проблему: вы можете генерировать идентификаторы на клиенте, сервере или в любом другом месте, не опасаясь коллизий. Библиотека google/uuid поддерживает UUID версий 1 (на основе времени и MAC-адреса), 4 (случайные) и 5 (на основе хеша SHA-1).
Библиотека активно используется в таких проектах, как Kubernetes, Docker, CockroachDB и многих других. Она оптимизирована для высокой производительности, поддерживает потокобезопасную генерацию и предоставляет удобный API для всех основных операций с UUID.
2. Установка
Для установки библиотеки выполните команду в терминале:
go get github.com/google/uuid
После этого импортируйте пакет в своем коде:
import "github.com/google/uuid"
Убедитесь, что у вас установлен Go версии 1.16 или выше. Если вы используете Go modules (рекомендуется), библиотека автоматически добавится в ваш go.mod и go.sum файлы.
3. Быстрый старт — минимальный рабочий пример
Вот самый простой пример генерации UUID версии 4 (случайный):
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
// Генерируем новый UUID версии 4
id := uuid.New()
fmt.Println("Сгенерированный UUID:", id.String())
// Проверяем, что это действительно UUID
fmt.Println("Тип UUID:", id.Version())
fmt.Println("Вариант UUID:", id.Variant())
}
Вывод программы будет выглядеть примерно так:
Сгенерированный UUID: 6ba7b810-9dad-11d1-80b4-00c04fd430c8
Тип UUID: 4
Вариант: RFC 4122
4. Основные методы и функции
4.1. uuid.New()
Сигнатура: func New() UUID
Описание: Генерирует новый UUID версии 4 (случайный). Это самый популярный метод для создания уникальных идентификаторов. Использует криптостойкий генератор случайных чисел.
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
id := uuid.New()
fmt.Printf("UUID: %s\n", id.String())
fmt.Printf("Версия: %d\n", id.Version())
fmt.Printf("Вариант: %d\n", id.Variant())
}
4.2. uuid.NewUUID()
Сигнатура: func NewUUID() (UUID, error)
Описание: Генерирует UUID версии 1 на основе текущего времени и MAC-адреса. Может вернуть ошибку, если не удалось получить MAC-адрес.
package main
import (
"fmt"
"log"
"github.com/google/uuid"
)
func main() {
id, err := uuid.NewUUID()
if err != nil {
log.Fatal("Ошибка генерации UUID:", err)
}
fmt.Printf("UUID v1: %s\n", id.String())
fmt.Printf("Время создания: %v\n", id.Time())
fmt.Printf("MAC-адрес: %x\n", id.Node())
}
4.3. uuid.NewRandom()
Сигнатура: func NewRandom() (UUID, error)
Описание: Аналог New(), но возвращает ошибку, если не удалось получить случайные байты. Позволяет обработать ошибку генерации.
package main
import (
"fmt"
"log"
"github.com/google/uuid"
)
func main() {
id, err := uuid.NewRandom()
if err != nil {
log.Fatal("Ошибка генерации случайного UUID:", err)
}
fmt.Printf("Случайный UUID: %s\n", id.String())
}
4.4. uuid.NewSHA1()
Сигнатура: func NewSHA1(space UUID, data []byte) UUID
Описание: Создает UUID версии 5 на основе SHA-1 хеша от пространства имен и данных. Гарантирует, что одинаковые входные данные дадут одинаковый UUID.
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
// Используем стандартное пространство имен DNS
space := uuid.NameSpaceDNS
data := []byte("example.com")
id := uuid.NewSHA1(space, data)
fmt.Printf("UUID v5 для example.com: %s\n", id.String())
// Повторный вызов даст тот же UUID
id2 := uuid.NewSHA1(space, data)
fmt.Printf("Повторный вызов: %s\n", id2.String())
fmt.Printf("UUID совпадают: %v\n", id == id2)
}
4.5. uuid.Parse()
Сигнатура: func Parse(s string) (UUID, error)
Описание: Парсит строку в UUID. Поддерживает различные форматы: с дефисами, без дефисов, с фигурными скобками.
package main
import (
"fmt"
"log"
"github.com/google/uuid"
)
func main() {
// Разные форматы строк UUID
inputs := []string{
"6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"6ba7b8109dad11d180b400c04fd430c8",
"{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
}
for _, input := range inputs {
id, err := uuid.Parse(input)
if err != nil {
log.Printf("Ошибка парсинга '%s': %v", input, err)
continue
}
fmt.Printf("Распарсено: '%s' -> %s\n", input, id.String())
}
}
4.6. uuid.Must()
Сигнатура: func Must(uuid UUID, err error) UUID
Описание: Утилита для случаев, когда ошибка невозможна или должна вызвать панику. Часто используется с Parse для констант.
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
// Используем Must для константных UUID
id := uuid.Must(uuid.Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
fmt.Printf("UUID: %s\n", id.String())
// Must также работает с NewRandom, но это опасно
id2 := uuid.Must(uuid.NewRandom())
fmt.Printf("Случайный UUID: %s\n", id2.String())
}
4.7. uuid.ClockSequence()
Сигнатура: func (u UUID) ClockSequence() int
Описание: Возвращает номер последовательности часов для UUID версии 1. Для других версий возвращает 0.
package main
import (
"fmt"
"log"
"github.com/google/uuid"
)
func main() {
id, err := uuid.NewUUID()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Clock sequence: %d\n", id.ClockSequence())
fmt.Printf("Версия: %d\n", id.Version())
}
4.8. uuid.Node()
Сигнатура: func (u UUID) Node() []byte
Описание: Возвращает MAC-адрес узла для UUID версии 1. Для других версий возвращает nil.
package main
import (
"fmt"
"log"
"github.com/google/uuid"
)
func main() {
id, err := uuid.NewUUID()
if err != nil {
log.Fatal(err)
}
node := id.Node()
fmt.Printf("MAC-адрес: %02x:%02x:%02x:%02x:%02x:%02x\n",
node[0], node[1], node[2], node[3], node[4], node[5])
}
4.9. uuid.Time()
Сигнатура: func (u UUID) Time() Time
Описание: Возвращает время создания для UUID версии 1. Возвращает нулевое время для других версий.
package main
import (
"fmt"
"log"
"time"
"github.com/google/uuid"
)
func main() {
id, err := uuid.NewUUID()
if err != nil {
log.Fatal(err)
}
uuidTime := id.Time()
goTime := time.Time(uuidTime)
fmt.Printf("Время создания UUID: %s\n", goTime.Format(time.RFC3339))
fmt.Printf("Прошло с момента создания: %v\n", time.Since(goTime))
}
4.10. uuid.Version()
Сигнатура: func (u UUID) Version() int
Описание: Возвращает версию UUID (1, 4 или 5). Полезно для проверки типа UUID.
package main
import (
"fmt"
"github.com/google/uuid"
)
func main() {
uuids := []uuid.UUID{
uuid.New(),
uuid.Must(uuid.NewUUID()),
uuid.NewSHA1(uuid.NameSpaceDNS, []byte("test")),
}
for _, id := range uuids {
fmt.Printf("UUID: %s, Версия: %d\n", id.String(), id.Version())
}
}
5. Реальный практический пример
Рассмотрим реальный пример — систему управления пользователями, где UUID используются для идентификации пользователей, сессий и токенов.
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"log"
"sync"
"time"
"github.com/google/uuid"
)
// User представляет пользователя системы
type User struct {
ID uuid.UUID
Email string
CreatedAt time.Time
UpdatedAt time.Time
}
// Session представляет сессию пользователя
type Session struct {
ID uuid.UUID
UserID uuid.UUID
Token string
ExpiresAt time.Time
}
// UserStore — потокобезопасное хранилище пользователей
type UserStore struct {
mu sync.RWMutex
users map[uuid.UUID]*User
sessions map[string]*Session
}
// NewUserStore создает новое хранилище
func NewUserStore() *UserStore {
return &UserStore{
users: make(map[uuid.UUID]*User),
sessions: make(map[string]*Session),
}
}
// CreateUser создает нового пользователя
func (s *UserStore) CreateUser(email string) (*User, error) {
s.mu.Lock()
defer s.mu.Unlock()
// Проверяем, что email уникален
for _, user := range s.users {
if user.Email == email {
return nil, fmt.Errorf("пользователь с email %s уже существует", email)
}
}
user := &User{
ID: uuid.New(),
Email: email,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
s.users[user.ID] = user
return user, nil
}
// GetUser возвращает пользователя по ID
func (s *UserStore) GetUser(id uuid.UUID) (*User, error) {
s.mu.RLock()
defer s.mu.RUnlock()
user, exists := s.users[id]
if !exists {
return nil, fmt.Errorf("пользователь с ID %s не найден", id)
}
return user, nil
}
// CreateSession создает новую сессию для пользователя
func (s *UserStore) CreateSession(userID uuid.UUID) (*Session, error) {
s.mu.Lock()
defer s.mu.Unlock()
// Проверяем существование пользователя
if _, exists := s.users[userID]; !exists {
return nil, fmt.Errorf("пользователь не найден")
}
// Генерируем токен на основе UUID и времени
tokenData := fmt.Sprintf("%s:%d", uuid.New().String(), time.Now().UnixNano())
hash := sha256.Sum256([]byte(tokenData))
token := hex.EncodeToString(hash[:])
session := &Session{
ID: uuid.New(),
UserID: userID,
Token: token,
ExpiresAt: time.Now().Add(24 * time.Hour), // Сессия на 24 часа
}
s.sessions[session.Token] = session
return session, nil
}
// ValidateSession проверяет валидность сессии
func (s *UserStore) ValidateSession(token string) (*Session, error) {
s.mu.RLock()
defer s.mu.RUnlock()
session, exists := s.sessions[token]
if !exists {
return nil, fmt.Errorf("сессия не найдена")
}
if time.Now().After(session.ExpiresAt) {
return nil, fmt.Errorf("сессия истекла")
}
return session, nil
}
// GenerateAPIKey генерирует API ключ на основе UUID v5
func GenerateAPIKey(userID uuid.UUID, service string) uuid.UUID {
// Используем пространство имен для API ключей
namespace := uuid.Must(uuid.Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
data := []byte(fmt.Sprintf("%s:%s", userID.String(), service))
return uuid.NewSHA1(namespace,