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

Shiny应用中用户自定义Spotify凭证认证失败问题排查求助

问题排查与解决方案

核心问题分析

出现400错误的原因主要有三点:

  1. 认证逻辑触发时机不匹配:bar_plot反应式会在用户点击「Get data」时直接执行API调用,但此时用户输入的凭证可能还未完成认证,环境变量未正确设置。
  2. Token缓存冲突:get_spotify_access_token默认会缓存已获取的token,硬编码凭证时的有效token会被缓存,当用户输入新凭证时,缓存未清空,导致仍使用旧token发起请求。
  3. 环境变量作用域问题:在Shiny反应式上下文中设置Sys.setenv,可能存在作用域限制,无法确保后续API调用能读取到最新的凭证。

修正方案

1. 直接传递凭证参数,避免依赖环境变量

修改认证逻辑,直接将用户输入的Client ID和Secret传入get_spotify_access_token,同时将获取到的token存储为反应式变量,后续API调用时直接传入该token。

2. 控制交互流程,确保认证完成后再允许数据请求

添加状态控制,比如在未完成认证时禁用「Get data」按钮,避免用户提前触发API调用。

3. 使用安全输入组件存储Secret

将textInput替换为passwordInput,避免Client Secret明文显示。

修正后的完整代码

library(shiny)
library(spotifyr)
library(plotly)
library(dplyr)

ui <- fluidPage(title = "Bar Plot",
                sidebarLayout(
                  sidebarPanel(
                    textInput("id","Enter Client ID"),
                    passwordInput("secret","Enter Client Secret"), # 改为密码输入框
                    actionButton("auth","Authenticate"),br(),hr(),
                    textInput("text","Enter Artist's ID"),
                    actionButton("click","Get data", disabled = TRUE), # 默认禁用
                    width = 3
                  ),
                  mainPanel(
                    fluidRow(column(6,plotlyOutput("plot",width = "105%",height = "400px")))
                  )
                ))

server <- function(input,output,session){
  
  # 存储认证后的token
  access_token <- reactiveVal(NULL)
  
  # 认证函数:直接传入凭证,返回token
  authentication <- function(id, secret) {
    req(id, secret)
    get_spotify_access_token(
      client_id = id,
      client_secret = secret,
      show_dialog = FALSE # 静默获取token
    )
  }
  
  # 点击认证按钮触发
  observeEvent(input$auth, {
    tryCatch({
      token <- authentication(input$id, input$secret)
      access_token(token)
      # 认证成功后启用Get data按钮
      enable("click")
      showNotification("认证成功!", type = "message")
    }, error = function(e) {
      showNotification(paste("认证失败:", e$message), type = "error")
      access_token(NULL)
      disable("click")
    })
  })
  
  artists_popularity <- function(artist_id, token){
    req(artist_id, token)
    
    # 传入token调用API
    related_artists <- get_related_artists(
      id = artist_id,
      include_meta_info = TRUE,
      access_token = token
    )
    
    other_related <- c()
    for(i in 1:nrow(related_artists$artists)){
      result <- get_related_artists(
        id = related_artists$artists[["id"]][i],
        include_meta_info = TRUE,
        access_token = token
      )
      other_related <- append(other_related,result)
    }
    
    nodes <- data.frame(
      id = c(1:400),
      node = c(other_related[[1]]$name,
               other_related[[2]]$name,
               other_related[[3]]$name,
               other_related[[4]]$name,
               other_related[[5]]$name,
               other_related[[6]]$name,
               other_related[[7]]$name,
               other_related[[8]]$name,
               other_related[[9]]$name,
               other_related[[10]]$name,
               other_related[[11]]$name,
               other_related[[12]]$name,
               other_related[[13]]$name,
               other_related[[14]]$name,
               other_related[[15]]$name,
               other_related[[16]]$name,
               other_related[[17]]$name,
               other_related[[18]]$name,
               other_related[[19]]$name,
               other_related[[20]]$name),
      
      label = c(other_related[[1]]$name,
                other_related[[2]]$name,
                other_related[[3]]$name,
                other_related[[4]]$name,
                other_related[[5]]$name,
                other_related[[6]]$name,
                other_related[[7]]$name,
                other_related[[8]]$name,
                other_related[[9]]$name,
                other_related[[10]]$name,
                other_related[[11]]$name,
                other_related[[12]]$name,
                other_related[[13]]$name,
                other_related[[14]]$name,
                other_related[[15]]$name,
                other_related[[16]]$name,
                other_related[[17]]$name,
                other_related[[18]]$name,
                other_related[[19]]$name,
                other_related[[20]]$name),
      
      popularity = c(c(other_related[[1]]$popularity,
                       other_related[[2]]$popularity,
                       other_related[[3]]$popularity,
                       other_related[[4]]$popularity,
                       other_related[[5]]$popularity,
                       other_related[[6]]$popularity,
                       other_related[[7]]$popularity,
                       other_related[[8]]$popularity,
                       other_related[[9]]$popularity,
                       other_related[[10]]$popularity,
                       other_related[[11]]$popularity,
                       other_related[[12]]$popularity,
                       other_related[[13]]$popularity,
                       other_related[[14]]$popularity,
                       other_related[[15]]$popularity,
                       other_related[[16]]$popularity,
                       other_related[[17]]$popularity,
                       other_related[[18]]$popularity,
                       other_related[[19]]$popularity,
                       other_related[[20]]$popularity)),
      
      followers = c(c(other_related[[1]]$followers.total,
                      other_related[[2]]$followers.total,
                      other_related[[3]]$followers.total,
                      other_related[[4]]$followers.total,
                      other_related[[5]]$followers.total,
                      other_related[[6]]$followers.total,
                      other_related[[7]]$followers.total,
                      other_related[[8]]$followers.total,
                      other_related[[9]]$followers.total,
                      other_related[[10]]$followers.total,
                      other_related[[11]]$followers.total,
                      other_related[[12]]$followers.total,
                      other_related[[13]]$followers.total,
                      other_related[[14]]$followers.total,
                      other_related[[15]]$followers.total,
                      other_related[[16]]$followers.total,
                      other_related[[17]]$followers.total,
                      other_related[[18]]$followers.total,
                      other_related[[19]]$followers.total,
                      other_related[[20]]$followers.total))
    )
    
    nodes <- distinct(nodes,node,label,popularity,followers,.keep_all = T)
    
    nodes_df <- data.frame(
      Artist = reorder(nodes$node,+nodes$popularity),
      Popularity = nodes$popularity
    )
    
    nodes_df <- head(nodes_df,20)
    
    fill <- c("A","B","C","D","E","F","G","H")
    rand_fill <- sample(fill,size = 1)
    
    ggnodes <- nodes_df |>
      ggplot()+aes(x = Artist,y = Popularity,fill = Artist)+
      geom_bar(stat = "identity")+coord_flip()+
      ggtitle("Top 20 Most Popular Artists")+
      labs(x = "Artists")+scale_fill_viridis(discrete = T,option = rand_fill)+
      scale_y_continuous(expand = c(0,0))+
      theme(axis.ticks = element_blank(),
            legend.position = "none",
            axis.text = element_text(size = 10,face = "bold"),
            plot.title = element_text(size = 12,face = "bold",hjust = 0.5),
            axis.title.x = element_text(size = 12,face = "bold"),
            axis.title.y = element_text(hjust = 0.8,size = 12,face = "bold"))
    
    g <- ggnodes |> ggplotly(tooltip = c("x","y"))
    
    return(g)
  }
  
  id_input <- reactive({as.character(input$text)})
  
  # 点击Get data时触发,传入token
  plot_react <- eventReactive(input$click,{
    artists_popularity(id_input(), access_token())
  })
  
  output$plot <- renderPlotly({
    plot_react()
  })
  
}

shinyApp(ui = ui,server = server)

关键修改点说明

  • 使用reactiveVal存储认证后的token,确保在反应式上下文中能正确传递。
  • 认证函数直接传入凭证参数调用get_spotify_access_token,避免环境变量依赖。
  • 添加按钮状态控制:认证成功前禁用「Get data」按钮,防止无效请求。
  • 所有Spotify API调用(get_related_artists)都显式传入access_token参数,确保使用最新的有效token。
  • 替换textInput为passwordInput存储Client Secret,提升安全性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 20:55:20