Files
backend/internal/service/livekit_service.go
lan d9aa4b46c3
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00

128 lines
3.4 KiB
Go

package service
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"time"
"with_you/internal/config"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/livekit"
"go.uber.org/zap"
"google.golang.org/protobuf/encoding/protojson"
)
// RoomConfig 房间配置选项
type RoomConfig struct {
MaxParticipants uint32
EmptyTimeout uint32 // 秒
}
// LiveKitService LiveKit 令牌生成和 Webhook 处理服务
type LiveKitService interface {
GenerateToken(roomName, userID string, canPublish, canSubscribe bool, roomCfg ...RoomConfig) (string, error)
ProcessWebhook(ctx context.Context, body []byte, authHeader string) (*livekit.WebhookEvent, error)
}
type liveKitService struct {
config *config.LiveKitConfig
logger *zap.Logger
}
// NewLiveKitService 创建 LiveKit 服务
func NewLiveKitService(cfg *config.Config, logger *zap.Logger) LiveKitService {
return &liveKitService{
config: &cfg.LiveKit,
logger: logger,
}
}
// GenerateToken 为指定 room 和 user 生成 LiveKit 访问令牌
func (s *liveKitService) GenerateToken(roomName, userID string, canPublish, canSubscribe bool, roomCfg ...RoomConfig) (string, error) {
if !s.config.Enabled {
return "", fmt.Errorf("livekit is not enabled")
}
ttl := s.config.TokenTTL
if ttl <= 0 {
ttl = 600 // 默认 10 分钟
}
at := auth.NewAccessToken(s.config.APIKey, s.config.APISecret)
at.SetName(userID)
at.SetIdentity(userID)
at.SetValidFor(time.Duration(ttl) * time.Second)
grant := &auth.VideoGrant{
RoomJoin: true,
Room: roomName,
CanPublish: &canPublish,
CanSubscribe: &canSubscribe,
CanPublishData: &canPublish,
}
at.SetVideoGrant(grant)
token, err := at.ToJWT()
if err != nil {
s.logger.Error("failed to generate livekit token", zap.Error(err))
return "", fmt.Errorf("generate token: %w", err)
}
return token, nil
}
// ProcessWebhook 验证并解析 LiveKit Webhook 事件
func (s *liveKitService) ProcessWebhook(ctx context.Context, body []byte, authHeader string) (*livekit.WebhookEvent, error) {
if s.config.WebhookSecret != "" {
if err := s.verifyWebhookSignature(body, authHeader); err != nil {
return nil, fmt.Errorf("webhook signature verification failed: %w", err)
}
}
var event livekit.WebhookEvent
if err := protojson.Unmarshal(body, &event); err != nil {
return nil, fmt.Errorf("failed to unmarshal webhook event: %w", err)
}
s.logger.Info("received livekit webhook",
zap.String("event", event.Event),
zap.String("room_name", event.Room.GetName()),
)
return &event, nil
}
// verifyWebhookSignature 验证 Webhook 签名
func (s *liveKitService) verifyWebhookSignature(body []byte, authHeader string) error {
// LiveKit webhook 使用 SHA256 HMAC 签名
// Authorization header 格式: "sha256=<base64 encoded signature>"
if authHeader == "" {
return fmt.Errorf("missing authorization header")
}
expectedPrefix := "sha256="
if len(authHeader) < len(expectedPrefix) || authHeader[:len(expectedPrefix)] != expectedPrefix {
return fmt.Errorf("invalid authorization header format")
}
expectedSig, err := base64.StdEncoding.DecodeString(authHeader[len(expectedPrefix):])
if err != nil {
return fmt.Errorf("failed to decode signature: %w", err)
}
mac := hmac.New(sha256.New, []byte(s.config.WebhookSecret))
mac.Write(body)
actualSig := mac.Sum(nil)
if !hmac.Equal(actualSig, expectedSig) {
return fmt.Errorf("signature mismatch")
}
return nil
}