深夜提醒

现在是深夜,建议您注意休息,不要熬夜哦~

🏮 🏮 🏮

新年快乐

祝君万事如意心想事成!

share-image
中
ESC

Building a Highly Scalable Music Streaming Backend: Go + Gin + OSS in Practice

English 阅读中文版

This article documents in detail the construction of a complete music player backend service, covering the full implementation of core technologies such as Gin framework setup, JWT authentication, Alibaba Cloud OSS object storage, audio format conversion, scheduled task scheduling, and database design.

Table of Contents

  1. Project Overview and Architecture Design
  2. Technology Stack Selection and Project Structure
  3. Configuration Management and Environment Isolation
  4. Database Design and Model Relationships
  5. JWT Authentication and Access Control
  6. Alibaba Cloud OSS Integration and File Storage
  7. Audio Format Conversion Service
  8. Scheduled Tasks and Data Deduplication
  9. Deployment and Operations in Practice
  10. Summary and Outlook

1. Project Overview and Architecture Design

1.1 Project Background

Melody is the backend service of a local music player, and it needs to support the following core features:

  • User System: registration, login, JWT authentication, personal profile management
  • Song Management: CRUD operations, metadata extraction, audio format conversion
  • Albums and Artists: complete music metadata management
  • Playlist System: user-defined playlists, favorites
  • Play Statistics: recently played, play count statistics
  • File Storage: Alibaba Cloud OSS object storage, signed URL downloads

1.2 Overall Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Client Layer │
│ Flutter App │ Web Admin │
└──────────────────────────────┼──────────────────────────────────┘
│ HTTPS
┌──────────────────────────────▼──────────────────────────────────┐
│ API Gateway │
│ Nginx (load balancing, SSL termination, static) │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────────┐
│ Go Gin Server (8080) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Auth API │ │ Song API │ │ Playlist │ │ Upload │ │
│ │ (JWT auth) │ │(song mgmt) │ │(playlist) │ │(file up) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ ┌──────▼───────────────▼───────────────▼──────────────▼─────┐ │
│ │ Middleware Layer │ │
│ │ JWTAuth │ RateLimit │ CORS │ Security │ Logging │ Recover │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ MySQL 8 │ │ Alibaba │ │ ffmpeg │
│ (music meta)│ │ Cloud OSS │ │ (format │
│ │ │ (audio file)│ │ conversion)│
└─────────────┘ └─────────────┘ └─────────────┘

1.3 Core Features

Feature Technical Implementation Notes
Auth & Authorization JWT + bcrypt Stateless auth, encrypted password storage
File Storage Alibaba Cloud OSS Supports both server-side proxy and client-side direct upload
Audio Processing ffmpeg Supports mutual conversion between MP3/FLAC/WAV/AAC and more
Scheduled Tasks robfig/cron Automatically deduplicates music files daily
Configuration Management Viper Supports YAML config files and environment variables

2. Technology Stack Selection and Project Structure

2.1 Core Technology Stack

// go.mod
module melody-backend

go 1.21

require (
github.com/gin-gonic/gin v1.9.1 // Web framework
github.com/golang-jwt/jwt/v5 v5.2.0 // JWT authentication
gorm.io/gorm v1.25.7 // ORM
gorm.io/driver/mysql v1.5.4 // MySQL driver
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // Alibaba Cloud OSS
github.com/spf13/viper v1.18.2 // Configuration management
github.com/robfig/cron/v3 v3.0.1 // Scheduled tasks
golang.org/x/crypto v0.19.0 // Password encryption
)

2.2 Project Structure

melody_backend/
├── cmd/
│ └── main.go # Program entry point
├── internal/
│ ├── config/ # Configuration management
│ │ ├── config.go # Config structs and loading
│ │ └── database.go # Database connection
│ ├── handlers/ # HTTP handlers
│ │ ├── auth.go # Authentication
│ │ ├── song.go # Song management
│ │ ├── album.go # Album management
│ │ ├── artist.go # Artist management
│ │ ├── playlist.go # Playlist management
│ │ ├── favorite.go # Favorites
│ │ ├── recent.go # Recently played
│ │ └── upload.go # File upload
│ ├── middleware/ # Middleware
│ │ ├── auth.go # JWT authentication
│ │ ├── cors.go # CORS handling
│ │ └── error_handler.go # Error handling
│ ├── models/ # Data models
│ │ └── models.go # All model definitions
│ ├── routes/ # Route configuration
│ │ └── routes.go # Route registration
│ └── services/ # Business services
│ ├── audio_converter.go # Audio format conversion
│ ├── deduplicate.go # Deduplication service
│ └── scheduler.go # Scheduled task scheduling
├── pkg/
│ └── oss/ # OSS client wrapper
│ └── client.go
├── scripts/
│ └── init-db.go # Database initialization
├── config.yaml # Config file
├── go.mod
└── README.md

3. Configuration Management and Environment Isolation

3.1 Configuration Structure Design

Using Viper to implement flexible configuration management, supporting both YAML files and environment variables:

type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
JWT JWTConfig `mapstructure:"jwt"`
OSS OSSConfig `mapstructure:"oss"`
Upload UploadConfig `mapstructure:"upload"`
}

type DatabaseConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Name string `mapstructure:"name"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
MaxIdleConns int `mapstructure:"max_idle_conns"`
MaxOpenConns int `mapstructure:"max_open_conns"`
}

type OSSConfig struct {
Region string `mapstructure:"region"`
AccessKeyID string `mapstructure:"access_key_id"`
AccessKeySecret string `mapstructure:"access_key_secret"`
Bucket string `mapstructure:"bucket"`
Endpoint string `mapstructure:"endpoint"`
DownloadExpire int64 `mapstructure:"download_expire"`
}

3.2 Configuration Loading Logic

func Load() (*Config, error) {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.AddConfigPath(".")

// Set default values
viper.SetDefault("server.port", 8080)
viper.SetDefault("database.max_idle_conns", 10)
viper.SetDefault("database.max_open_conns", 100)
viper.SetDefault("jwt.expires_in", "168h")

// Support environment variables (MELODY_ prefix)
viper.SetEnvPrefix("MELODY")
viper.AutomaticEnv()

// Read config file
if err := viper.ReadInConfig(); err != nil {
return nil, err
}

// Override from environment variables (systemd style)
loadFromEnv(&config)

return &config, nil
}

3.3 Production Environment Configuration Recommendations

# config.yaml (production environment)
server:
port: 8080
mode: release # gin.ReleaseMode

database:
host: ${DB_HOST}
port: 3306
name: melody_db
user: ${DB_USER}
password: ${DB_PASSWORD}
max_idle_conns: 10
max_open_conns: 100

jwt:
secret: ${JWT_SECRET}
expires_in: 168h # 7 days

oss:
region: oss-cn-hangzhou
access_key_id: ${OSS_ACCESS_KEY_ID}
access_key_secret: ${OSS_ACCESS_KEY_SECRET}
bucket: melody-music
download_expire: 3600 # Signed URL valid for 1 hour

4. Database Design and Model Relationships

4.1 ER Diagram Relationships

┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│ users │ │ artists │ │ albums │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ id │ │ id │ │ id │
│ username │ │ name │ │ title │
│ email │ │ bio │◄──────┤ artist_id │
│ password │ │ avatar_url │ │ cover_url │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ ┌──────┴──────┐ │
│ │ │ │
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ playlists │ │ songs │ │ favorites │
├─────────────┤ ├─────────────┤ ├─────────────┤
│ id │ │ id │ │ id │
│ user_id │──┤ title │ │ user_id │
│ name │ │ artist_id │──┤ song_id │
│ description │ │ album_id │──┘ │
└─────────────┘ │ file_url │ │
│ duration │ │
└─────────────┘ │
▲ │
│ │
┌──────┴──────┐ │
│playlist_songs│ │
├─────────────┤ │
│playlist_id │ │
│song_id │──────────────┘
└─────────────┘

4.2 Core Model Definitions

// User model
type User struct {
ID uint `gorm:"primarykey" json:"id"`
Username string `gorm:"uniqueIndex;size:50" json:"username"`
Email string `gorm:"uniqueIndex;size:100" json:"email"`
Password string `gorm:"size:255" json:"-"` // Not serialized
Avatar string `gorm:"size:255" json:"avatar"`
CreatedAt time.Time `json:"created_at"`
}

// Song model
type Song struct {
ID uint `gorm:"primarykey" json:"id"`
Title string `gorm:"size:200;index" json:"title"`
ArtistID *uint `json:"artist_id"`
Artist *Artist `json:"artist,omitempty"`
AlbumID *uint `json:"album_id"`
Album *Album `json:"album,omitempty"`
FileURL string `gorm:"size:500" json:"file_url"`
Duration float64 `json:"duration"`
Lyrics string `gorm:"type:text" json:"lyrics"`
PlayCount int `gorm:"default:0" json:"play_count"`
CreatedAt time.Time `json:"created_at"`
}

// Playlist model
type Playlist struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `json:"user_id"`
Name string `gorm:"size:100" json:"name"`
Description string `gorm:"type:text" json:"description"`
IsPublic bool `gorm:"default:false" json:"is_public"`
Songs []Song `gorm:"many2many:playlist_songs;" json:"songs,omitempty"`
CreatedAt time.Time `json:"created_at"`
}

4.3 Database Connection Pool Optimization

func InitDB(cfg *config.DatabaseConfig) (*gorm.DB, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.Name)

db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent), // Disable SQL logging in production
})
if err != nil {
return nil, err
}

// Configure connection pool
sqlDB, _ := db.DB()
sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)
sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
sqlDB.SetConnMaxLifetime(time.Hour)

return db, nil
}

5. JWT Authentication and Access Control

5.1 JWT Token Generation and Verification

package middleware

import (
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"time"
)

type Claims struct {
UserID uint `json:"user_id"`
Email string `json:"email"`
jwt.RegisteredClaims
}

// GenerateToken generates a JWT Token
func GenerateToken(userID uint, email string, secret string, expiresIn time.Duration) (string, error) {
claims := Claims{
UserID: userID,
Email: email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiresIn)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(secret))
}

// AuthMiddleware is the JWT authentication middleware
func AuthMiddleware(secret string) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(401, gin.H{"error": "missing authorization header"})
c.Abort()
return
}

// Bearer token format
tokenString := strings.TrimPrefix(authHeader, "Bearer ")

token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})

if err != nil || !token.Valid {
c.JSON(401, gin.H{"error": "invalid token"})
c.Abort()
return
}

claims, _ := token.Claims.(*Claims)
c.Set("userID", claims.UserID)
c.Set("email", claims.Email)
c.Next()
}
}

5.2 Route Permission Grouping

func SetupRoutes(r *gin.Engine, db *gorm.DB, cfg *config.Config) {
// Public routes
public := r.Group("/api/v1")
{
public.POST("/auth/register", handlers.Register)
public.POST("/auth/login", handlers.Login)
public.GET("/songs", handlers.GetSongs) // Public access
public.GET("/songs/:id", handlers.GetSong)
}

// Routes requiring authentication
authorized := r.Group("/api/v1")
authorized.Use(middleware.AuthMiddleware(cfg.JWT.Secret))
{
authorized.GET("/user/profile", handlers.GetProfile)
authorized.POST("/playlists", handlers.CreatePlaylist)
authorized.POST("/favorites", handlers.AddFavorite)
authorized.POST("/songs/:id/play", handlers.RecordPlay)
}
}

6. Alibaba Cloud OSS Integration and File Storage

6.1 OSS Client Wrapper

package oss

import (
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"io"
"time"
)

type Client struct {
bucket *oss.Bucket
config Config
}

type Config struct {
Region string
AccessKeyID string
AccessKeySecret string
Bucket string
Endpoint string
DownloadExpire int64
}

var client *Client

// Init initializes the OSS client
func Init(cfg Config) error {
ossClient, err := oss.New(cfg.Endpoint, cfg.AccessKeyID, cfg.AccessKeySecret)
if err != nil {
return err
}

bucket, err := ossClient.Bucket(cfg.Bucket)
if err != nil {
return err
}

client = &Client{
bucket: bucket,
config: cfg,
}
return nil
}

// GetClient returns the OSS client instance
func GetClient() *Client {
if client == nil {
panic("OSS client not initialized")
}
return client
}

// Upload uploads a file to OSS
func (c *Client) Upload(objectKey string, reader io.Reader, contentType string) (string, error) {
options := []oss.Option{
oss.ContentType(contentType),
}

err := c.bucket.PutObject(objectKey, reader, options...)
if err != nil {
return "", err
}

// Return the public access URL (if it's a private bucket, a signed URL must be generated)
return c.bucket.BucketName + "." + c.config.Endpoint + "/" + objectKey, nil
}

// GetSignedURL returns a signed download URL
func (c *Client) GetSignedURL(objectKey string, expireSeconds int64) (string, error) {
return c.bucket.SignURL(objectKey, oss.HTTPGet, expireSeconds)
}

// GenerateObjectKey generates a unique object storage path
func (c *Client) GenerateObjectKey(prefix, filename string) string {
ext := filepath.Ext(filename)
uuid := uuid.New().String()
timestamp := time.Now().Unix()
return fmt.Sprintf("%s/%d_%s%s", prefix, timestamp, uuid[:8], ext)
}

6.2 Two Upload Modes

Mode 1: Server-side proxy upload (suitable for small files)

func UploadSong(c *gin.Context) {
file, header, err := c.Request.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": "invalid file"})
return
}
defer file.Close()

// Generate the object path
objectKey := oss.GetClient().GenerateObjectKey("music", header.Filename)

// Upload to OSS
url, err := oss.GetClient().Upload(objectKey, file, header.Header.Get("Content-Type"))
if err != nil {
c.JSON(500, gin.H{"error": "upload failed"})
return
}

// Save to the database
song := models.Song{
Title: strings.TrimSuffix(header.Filename, filepath.Ext(header.Filename)),
FileURL: url,
}
db.Create(&song)

c.JSON(200, gin.H{"data": song})
}

Mode 2: Client-side direct upload (recommended, reduces server load)

// Get a signed upload URL
func GetUploadURL(c *gin.Context) {
filename := c.Query("filename")
contentType := c.Query("content_type")

objectKey := oss.GetClient().GenerateObjectKey("music", filename)

// Generate a signed URL (allowing PUT requests)
signedURL, err := oss.GetClient().GetSignedURLForPut(objectKey, contentType, 300)
if err != nil {
c.JSON(500, gin.H{"error": "failed to generate URL"})
return
}

c.JSON(200, gin.H{
"upload_url": signedURL,
"object_key": objectKey,
})
}

// Client-side direct upload flow:
// 1. Call /api/v1/upload/url to get a signed URL
// 2. The client PUTs the file directly to OSS using that URL
// 3. After the upload completes, call the server API to save the file info

7. Audio Format Conversion Service

7.1 Audio Conversion Based on ffmpeg

type AudioConverter struct {
tempDir string
}

// ConvertToOSS converts audio and uploads it to OSS
func (ac *AudioConverter) ConvertToOSS(inputURL string, outputFormat string, options map[string]string) (*ConvertToOSSResult, error) {
taskID := uuid.New().String()
inputPath := filepath.Join(ac.tempDir, fmt.Sprintf("convert_%s_input", taskID))
outputPath := filepath.Join(ac.tempDir, fmt.Sprintf("convert_%s_output.%s", taskID, outputFormat))

defer os.Remove(inputPath)
defer os.Remove(outputPath)

// Step 1: Download the input file
if err := ac.downloadFile(inputURL, inputPath); err != nil {
return nil, fmt.Errorf("failed to download: %w", err)
}

// Step 2: Convert with ffmpeg
if err := ac.convertFile(inputPath, outputPath, outputFormat, options); err != nil {
return nil, fmt.Errorf("conversion failed: %w", err)
}

// Step 3: Read and upload
outputData, _ := os.ReadFile(outputPath)
duration, _ := ac.getAudioDuration(outputPath)

objectKey := oss.GetClient().GenerateObjectKey("music/converted",
fmt.Sprintf("%s.%s", baseName, outputFormat))
publicURL, err := oss.GetClient().Upload(objectKey, strings.NewReader(string(outputData)),
ac.getContentType(outputFormat))

return &ConvertToOSSResult{
PublicURL: publicURL,
Format: outputFormat,
Duration: duration,
FileSize: int64(len(outputData)),
}, nil
}

// convertFile performs the ffmpeg conversion
func (ac *AudioConverter) convertFile(inputPath, outputPath, outputFormat string, options map[string]string) error {
args := []string{"-i", inputPath, "-y"}

// Choose the encoder based on the format
switch strings.ToLower(outputFormat) {
case "mp3":
args = append(args, "-c:a", "libmp3lame", "-q:a", "0")
case "flac":
args = append(args, "-c:a", "flac")
case "wav":
args = append(args, "-c:a", "pcm_s16le")
case "aac":
args = append(args, "-c:a", "aac", "-b:a", "192k")
case "ogg":
args = append(args, "-c:a", "libvorbis", "-q:a", "4")
}

args = append(args, outputPath)

cmd := exec.Command("ffmpeg", args...)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("ffmpeg error: %v, output: %s", err, string(output))
}
return nil
}

7.2 Supported Audio Formats

Format Encoder Use Case
MP3 libmp3lame Universal compatibility, lossy compression
FLAC flac Lossless quality, larger files
WAV pcm_s16le Raw audio, uncompressed
AAC aac Preferred for Apple devices
OGG libvorbis Open source, royalty-free
Opus libopus Low-latency voice

8. Scheduled Tasks and Data Deduplication

8.1 Scheduled Task Scheduler

package services

import (
"github.com/robfig/cron/v3"
"log"
)

type Scheduler struct {
cron *cron.Cron
db *gorm.DB
}

func NewScheduler(db *gorm.DB) *Scheduler {
return &Scheduler{
cron: cron.New(),
db: db,
}
}

// Start starts the scheduled tasks
func (s *Scheduler) Start() {
// Run the deduplication task at 3 AM every day
s.cron.AddFunc("0 3 * * *", s.deduplicateSongs)

s.cron.Start()
log.Println("Scheduler started")
}

// Stop stops the scheduled tasks
func (s *Scheduler) Stop() {
s.cron.Stop()
}

// deduplicateSongs deduplicates songs
func (s *Scheduler) deduplicateSongs() {
log.Println("[Scheduler] Starting song deduplication...")

dedupService := NewDeduplicateService(s.db)
result, err := dedupService.Deduplicate()
if err != nil {
log.Printf("[Scheduler] Deduplication failed: %v", err)
return
}

log.Printf("[Scheduler] Deduplication complete: removed %d duplicates", result.RemovedCount)
}

8.2 Deduplication Service Implementation

// DeduplicateService is the deduplication service
type DeduplicateService struct {
db *gorm.DB
}

// DuplicateGroup is a group of duplicate songs
type DuplicateGroup struct {
Title string
ArtistID uint
AlbumID uint
IDs []uint
Count int
}

// Deduplicate performs deduplication, keeping the earliest created record
func (s *DeduplicateService) Deduplicate() (*DeduplicateResult, error) {
// Find duplicate songs (same title + artist + album)
var groups []struct {
Title string
ArtistID uint
AlbumID uint
Count int
MinID uint
}

s.db.Model(&models.Song{}).
Select("title, artist_id, album_id, COUNT(*) as count, MIN(id) as min_id").
Group("title, artist_id, album_id").
Having("count > 1").
Scan(&groups)

var removedCount int

for _, group := range groups {
// Delete all records in the group except the earliest one
result := s.db.Where("title = ? AND artist_id = ? AND album_id = ? AND id != ?",
group.Title, group.ArtistID, group.AlbumID, group.MinID).
Delete(&models.Song{})

if result.Error != nil {
log.Printf("Failed to delete duplicates for %s: %v", group.Title, result.Error)
continue
}

removedCount += int(result.RowsAffected)
log.Printf("Removed %d duplicates for song: %s", result.RowsAffected, group.Title)
}

return &DeduplicateResult{
RemovedCount: removedCount,
GroupsFound: len(groups),
}, nil
}

9. Deployment and Operations in Practice

9.1 systemd Service Configuration

# /etc/systemd/system/melody-server.service
[Unit]
Description=Melody Music Backend API
After=network.target

[Service]
Type=simple
User=melody
Group=melody
WorkingDirectory=/opt/melody
ExecStart=/opt/melody/melody-api
Restart=always
RestartSec=5

# Environment variables
Environment="SERVER_PORT=8080"
Environment="SERVER_MODE=release"
Environment="DB_HOST=localhost"
Environment="DB_PORT=3306"
Environment="DB_NAME=melody_db"
Environment="DB_USER=melody"
Environment="DB_PASSWORD=your_password"
Environment="JWT_SECRET=your_jwt_secret"
Environment="OSS_REGION=oss-cn-hangzhou"
Environment="OSS_ACCESS_KEY_ID=your_key"
Environment="OSS_ACCESS_KEY_SECRET=your_secret"
Environment="OSS_BUCKET=melody-music"

[Install]
WantedBy=multi-user.target

9.2 Nginx Reverse Proxy Configuration

upstream melody_backend {
server 127.0.0.1:8080;
keepalive 32;
}

server {
listen 443 ssl http2;
server_name music.yourdomain.com;

ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;

# API requests
location /api/ {
proxy_pass http://melody_backend/;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# Long connection optimization
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}

# Static resources (if the frontend is deployed on the same server)
location / {
root /opt/melody/web;
try_files $uri $uri/ /index.html;
}
}

9.3 Deployment Script

#!/bin/bash
# deploy.sh

set -e

APP_NAME="melody-api"
REMOTE_HOST="your-server"
REMOTE_DIR="/opt/melody"

echo "=== Building... ==="
GOOS=linux GOARCH=amd64 go build -o ${APP_NAME} cmd/main.go

echo "=== Deploying to ${REMOTE_HOST}... ==="
scp ${APP_NAME} ${REMOTE_HOST}:${REMOTE_DIR}/
scp config.yaml ${REMOTE_HOST}:${REMOTE_DIR}/

echo "=== Restarting service... ==="
ssh ${REMOTE_HOST} "sudo systemctl restart melody-server"

echo "=== Checking status... ==="
ssh ${REMOTE_HOST} "sudo systemctl status melody-server --no-pager"

echo "=== Deploy complete! ==="

10. Summary and Outlook

10.1 Project Highlights

  1. Clear architecture layering: handler → service → model three-tier architecture with clear responsibilities
  2. Flexible configuration management: Viper supports config files + environment variables, making deployment across environments easy
  3. Secure file storage: Alibaba Cloud OSS + signed URLs to keep resources safe
  4. Complete audio processing: ffmpeg-based support for mutual conversion between multiple formats
  5. Automated operations: scheduled deduplication tasks reduce manual intervention

10.2 Directions for Further Optimization

  • Cache optimization: integrate Redis to cache hot data (song lists, user info)
  • Search functionality: connect Elasticsearch for full-text search
  • Message queue: use RabbitMQ to handle asynchronous tasks (audio conversion, data import)
  • Rate limiting protection: implement API rate limiting based on the token bucket algorithm
  • Monitoring and alerting: integrate Prometheus + Grafana to monitor service status

References

文章作者:阿文
文章链接: https://www.awen.me/post/6a1f24cb.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿文的博客!
本文于 2026-02-26 17:00 发布,已超过半年(232天),请注意甄别内容是否已过期。

评论

0 条评论
😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️✋ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐⛎ ♈♉♊ ♋♌♍ ♎♏♐ ♑♒♓ 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘❌⭕ 🛑⛔📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭❗ ❕❓❔ ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️✅ 🈯💹❇️ ✳️❎🌐 💠Ⓜ️🌀 💤🏧🚾 ♿🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️
加载中...

留言反馈

😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️✋ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐⛎ ♈♉♊ ♋♌♍ ♎♏♐ ♑♒♓ 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘❌⭕ 🛑⛔📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭❗ ❕❓❔ ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️✅ 🈯💹❇️ ✳️❎🌐 💠Ⓜ️🌀 💤🏧🚾 ♿🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️