You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用zmb3/spotify获取用户播放曲目时遇错误46的问题排查

问题:Spotify OAuth后续请求报错(User not registered in the Developer Dashboard)

问题描述

首次获取用户当前播放曲目时正常,后续请求返回错误:

Error getting current playing song: spotify: couldn't decode error: (46) [User not registered in the Developer Dashboard

需要解决的疑问:

  • 该错误的原因是什么?
  • 是否无法复用其他用户的令牌?
  • 有没有无需让用户重复登录即可持续获取其播放曲目的方案?

相关代码

package src

import (
    ctx "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strings"

    "github.com/zmb3/spotify/v2"
    spotifyauth "github.com/zmb3/spotify/v2/auth"
    "golang.org/x/oauth2"
)

func CompleteAuth(w http.ResponseWriter, r *http.Request) {
    userID := r.URL.Query().Get("state")
    auth := spotifyauth.New(spotifyauth.WithRedirectURL(Env.RedirectUrl), spotifyauth.WithScopes(spotifyauth.ScopeUserReadCurrentlyPlaying), spotifyauth.WithClientID(Env.ClientID), spotifyauth.WithClientSecret(Env.ClientSecret))
    tok, err := auth.Token(ctx.Background(), userID, r)
    if err != nil {
        http.Error(w, "Couldn't get token", http.StatusForbidden)
        log.Fatal(err)
    }
    client := spotify.New(auth.Client(ctx.Background(), tok))
    info, err := client.CurrentUser(r.Context())
    if err != nil {
        http.Error(w, "Couldn't get user", http.StatusForbidden)
        log.Fatal(err)
    }
    db.Set(userID, tok.AccessToken, 0)
    w.Write([]byte(fmt.Sprintf("You are logged in as: %s\n", info.DisplayName)))
}

func IndexPage(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Hello!. Use /auth to login with spotify and /current to get your current playing song"))
}

func CurrentPage(w http.ResponseWriter, r *http.Request) {
    userid := r.URL.Query().Get("user_id")
    if userid == "" {
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte("You need to provide a user_id"))
        return
    }
    token, _ := db.Get(userid).Result()
    auth := spotifyauth.New(spotifyauth.WithRedirectURL(Env.RedirectUrl), spotifyauth.WithScopes(spotifyauth.ScopeUserReadCurrentlyPlaying), spotifyauth.WithClientID(Env.ClientID))
    fmt.Println(token)
    client := spotify.New(auth.Client(ctx.Background(), &oauth2.Token{AccessToken: token}))
    go RefreshToken(userid, client)
    user, err := client.PlayerCurrentlyPlaying(r.Context())
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte(fmt.Sprintf("Error getting current playing song: %s", err.Error())))
        return
    }
    response := PlayingInfo{
        Playing: user.Playing,
        Info:    nil,
    }
    if user.Playing {
        artists := make([]string, len(user.Item.Artists))
        for i, artist := range user.Item.Artists {
            artists[i] = artist.Name
        }
        response.Info = &PlayingResponse{
            SongName:      user.Item.Name,
            Artists:       strings.Join(artists, ", "),
            Link:          user.Item.ExternalURLs["spotify"],
            Duration:      user.Progress,
            TotalDuration: user.Item.Duration,
            Thumb:         user.Item.Album.Images[0].URL,
        }
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    if err := json.NewEncoder(w).Encode(response); err != nil {
        log.Println("Error encoding JSON:", err)
        http.Error(w, "Internal Server Error", http.StatusInternalServerError)
    }
}

func AuthSpotify(w http.ResponseWriter, r *http.Request) {
    userID := r.URL.Query().Get("user_id")
    if userID == "" {
        http.Error(w, "Invalid Params use ?user_id=<user_id>", http.StatusBadRequest)
        return
    }
    auth := spotifyauth.New(spotifyauth.WithRedirectURL(Env.RedirectUrl), spotifyauth.WithScopes(spotifyauth.ScopeUserReadCurrentlyPlaying), spotifyauth.WithClientID(Env.ClientID))
    url := auth.AuthURL(userID, oauth2.AccessTypeOffline)
    w.WriteHeader(http.StatusOK)
    w.Write([]byte(url))
}

func RefreshToken(userId string, client *spotify.Client) string {
    token, _ := db.Get(userId).Result()
    if token == "" {
        log.Println("User not logged in")
        return ""
    }
    auth := spotifyauth.New(spotifyauth.WithRedirectURL(Env.RedirectUrl), spotifyauth.WithScopes(spotifyauth.ScopeUserReadCurrentlyPlaying), spotifyauth.WithClientID(Env.ClientID), spotifyauth.WithClientSecret(Env.ClientSecret))
    c, err := spotify.New(auth.Client(ctx.Background(), &oauth2.Token{AccessToken: token})).Token()
    if err != nil {
        log.Println("Error refreshing token:", err)
        return ""
    }
    db.Set(userId, c.AccessToken, 0)
    return c.AccessToken

}

解答

错误原因

  1. 令牌存储不完整:你现在只存储了AccessToken,但Spotify OAuth的令牌体系需要RefreshToken才能实现自动刷新。首次请求时AccessToken未过期,能正常调用接口;过期后没有有效RefreshToken去获取新的AccessToken,使用失效的令牌请求就会触发这个错误。
  2. 刷新逻辑无效:RefreshToken函数里只传入了AccessToken,没有RefreshToken,根本无法完成令牌刷新。而且你在CompleteAuth中拿到的完整tok对象包含RefreshToken,但没有存储,后续彻底失去了刷新凭证。

关于令牌复用

不存在“无法复用其他用户令牌”的问题,每个用户的令牌是独立且可复用的——只要你正确维护令牌的生命周期(存储完整令牌、及时刷新),就能持续使用该用户的令牌调用接口。

持续获取播放曲目的方案

1. 完整存储令牌对象

修改CompleteAuth函数,将整个oauth2.Token序列化为JSON后存储,而不是只存AccessToken:

// 替换原来的db.Set(userID, tok.AccessToken, 0)
tokenJSON, err := json.Marshal(tok)
if err != nil {
    http.Error(w, "Failed to serialize token", http.StatusInternalServerError)
    return
}
db.Set(userID, string(tokenJSON), 0)

2. 修复令牌读取与客户端创建逻辑

修改CurrentPage函数,从数据库取出完整令牌并反序列化,再创建客户端:

func CurrentPage(w http.ResponseWriter, r *http.Request) {
    userid := r.URL.Query().Get("user_id")
    if userid == "" {
        w.WriteHeader(http.StatusBadRequest)
        w.Write([]byte("You need to provide a user_id"))
        return
    }
    tokenJSON, err := db.Get(userid).Result()
    if err != nil || tokenJSON == "" {
        w.WriteHeader(http.StatusUnauthorized)
        w.Write([]byte("User not logged in"))
        return
    }
    var tok oauth2.Token
    if err := json.Unmarshal([]byte(tokenJSON), &tok); err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte("Failed to parse token"))
        return
    }

    // 可以把auth实例全局初始化,不用每次创建
    auth := spotifyauth.New(
        spotifyauth.WithRedirectURL(Env.RedirectUrl),
        spotifyauth.WithScopes(spotifyauth.ScopeUserReadCurrentlyPlaying),
        spotifyauth.WithClientID(Env.ClientID),
        spotifyauth.WithClientSecret(Env.ClientSecret),
    )
    client := spotify.New(auth.Client(ctx.Background(), &tok))
    
    // 刷新令牌并更新存储
    newTok, err := client.Token()
    if err == nil {
        newTokJSON, _ := json.Marshal(newTok)
        db.Set(userid, string(newTokJSON), 0)
    }

    user, err := client.PlayerCurrentlyPlaying(r.Context())
    if err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        w.Write([]byte(fmt.Sprintf("Error getting current playing song: %s", err.Error())))
        return
    }
    // 后续响应逻辑不变...
}

3. 简化刷新逻辑

zmb3/spotify库的客户端会自动处理令牌刷新,只要你提供了完整的令牌(包含RefreshToken)和正确的auth配置(带ClientSecret),调用client.Token()就能自动刷新并返回新令牌,不需要自己重新创建客户端。


内容的提问来源于stack exchange,提问作者Mysterio007

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 22:15:12