我正在做一些音乐网站投资组合与Spotify,NextJS 我想在这个网站上播放音乐,需要Spotify认证才能使用它. 当我点击登录按钮并获得405 HTTP状态时. 我查了一下postman ,也发现了同样的错误. 我搜索,它可能是错误的GET/POST问题,但我不能处理在哪里修复它.

应用程序/API/登录代码部分

export async function LoginHandler (req: NextApiRequest, res: NextApiResponse) {
  const state = generateRandomString(16);
  const CLIENT_ID = process.env.NEXT_PUBLIC_SPOTIFY_CLIENT_ID;
  const scope =
    "user-read-private user-read-email user-read-playback-state user-modify-playback-state streaming";
  
  res.redirect(
    "https://accounts.spotify.com/authorize?"+
      qs.stringify({
        response_type: "code",
        client_id: CLIENT_ID,
        scope: scope,
        redirect_uri: `${REDIRECT_URL}/api/callback`,
        state: state,
      })
  );

我使用useRouter(下一步/导航)来路由它.

<button
        onClick={() => {
          router.push("/api/login");
        }}
      >

这个是获取令牌代码

export const getAccessTokenData = () => {
  return axios<AccessTokenData>({
    method: "POST",
    url: SPOTIFY_ACCESS_TOKEN_URL,
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization:
        "Basic " +
        Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64"),
    },
    data: {
      grant_type: "client_credentials",
    },
  });
};

export const getAuthorizationTokenData = (code: string) => {
  return axios<AuthorizationTokenData>({
    method: 'post',
    url: 'https://accounts.spotify.com/api/token',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Authorization:
        'Basic ' +
        Buffer.from(
          `${CLIENT_ID}:${CLIENT_SECRET}`
        ).toString('base64'),
    },
    data: {
      code: code,
      redirect_uri: REDIRECT_URL + '/api/callback',
      grant_type: 'authorization_code',
    },
  });
}

推荐答案

405错误表示身份验证无效或发送参数格式错误. 您的getAuthorizationTokenData()的S数据请求格式不正确.

要以axios POST调用的application/x—www—form—urlencoded格式发送数据,您应该使用URLSearchParams类来构造请求主体.

您需要了解Spotify的"授权码流" 更多详细信息请参见here

enter image description here

演示代码实现Spotify登录并获得访问令牌Authorization Code Flow,并获得特定的播放列表和显示歌曲.使用next.js.

File structure

我从Create next.js项目中添加了三个文件 (index. tsx,callbacl. tsx和constant. ts)

npx create-next-app@latest my-spotify-app --typescript

你需要删除./src/app/pages.tsx个文件.

enter image description here

index.tsx

// pages/index.tsx
import { GetServerSideProps } from 'next';
import { clientId, scope, redirectUri } from './constants';

const Home = ({ spotifyAuthUrl }: { spotifyAuthUrl: string }) => {
    return (
        <div>
            <h1>Spotify Login</h1>
            <a href={spotifyAuthUrl}>Login with Spotify</a>
        </div>
    );
};

export const getServerSideProps: GetServerSideProps = async () => {
    const encodeRedirectUri = encodeURIComponent(redirectUri);
    const spotifyAuthUrl = `https://accounts.spotify.com/authorize?response_type=code&client_id=${clientId}&redirect_uri=${encodeRedirectUri}&scope=${scope}`;
    return { props: { spotifyAuthUrl } };
};

export default Home;

节省callback.tsx

// pages/callback.tsx
import { GetServerSideProps } from 'next';
import axios from 'axios';
import { clientId, clientSecret, redirectUri, myPlaylistId } from './constants';

interface Track {
    track: {
        name: string;
        id: string;
    };
}

const Callback = ({ accessToken, playlistTracks }: { accessToken: string, playlistTracks: Track[] }) => {
    return (
        <div>
            <h1>Spotify Playlist Tracks</h1>
            <p>Access Token: {accessToken}</p>
            <ul>
                {playlistTracks.map((track, index) => (
                    <li key={index}>
                        {track.track.name}
                        <br />
                        <iframe
                            src={`https://open.spotify.com/embed/track/${track.track.id}`}
                            width="300"
                            height="80"
                            allow="encrypted-media"
                        ></iframe>
                    </li>
                ))}
            </ul>
        </div>
    );
};

export const getServerSideProps: GetServerSideProps = async ({ query }) => {
    const code = query.code as string;

    if (!code) {
        return {
            redirect: {
                destination: '/',
                permanent: false,
            },
        };
    }

    try {
        const response = await axios.post(
            'https://accounts.spotify.com/api/token',
            new URLSearchParams({
                grant_type: 'authorization_code',
                code,
                redirect_uri: redirectUri,
                client_id: clientId,
                client_secret: clientSecret,
            }).toString(),
            {
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
            }
        );

        const accessToken = response.data.access_token;

        const playlistResponse = await axios.get(
            `https://api.spotify.com/v1/playlists/${myPlaylistId}/tracks`,
            {
                headers: {
                    Authorization: `Bearer ${accessToken}`,
                },
            }
        );

        const playlistTracks: Track[] = playlistResponse.data.items;

        return { props: { accessToken, playlistTracks } };
    } catch (error) {
        console.error('Error:', error);
        return { props: { accessToken: '', playlistTracks: [] } };
    }
};

export default Callback;

节省constans.ts

// constants.ts
export const clientId = '[your client id]';
export const clientSecret = '[your client secret]';
export const redirectUri = 'http://localhost:3000/callback'; // replace your redirect URI
export const myPlaylistId = '[your playlist id]';
export const scope = 'playlist-read-private user-read-private user-read-email user-read-playback-state user-modify-playback-state streaming'; // Add more scopes if needed

Install dependency

npm insatall axios

Run it

npm run dev

Access it

http:/localhost:3000

Result

enter image description here

Typescript相关问答推荐

如何将http上下文附加到angular中翻译模块发送的请求

如何基于对象关键字派生类型?

node—redis:如何在redis timeSeries上查询argmin?

防止获取发出不需要的请求

如果一个变量不是never类型,我如何创建编译器错误?

在嵌套属性中使用Required

是否将自动导入样式从`~/目录/文件`改为`@/目录/文件`?

TypeScrip错误:为循环中的对象属性赋值

Typescript 中相同类型的不同结果

为什么特定的字符串不符合包括该字符串的枚举?

如何键入派生类的实例方法,以便它调用另一个实例方法?

如何使用TypeScrip在EXPO路由链路组件中使用动态路由?

类型实例化过深,可能是无限的&TypeScrip中的TupleUnion错误

如何将通用接口的键正确设置为接口的键

如何从JWT Reaction中提取特定值

是否可以定义引用另一行中的类型参数的类型?

如何实现允许扩展泛型函数参数的类型

为什么TS条件返回要求不明确的强制转换而不是精确的强制转换?

在对象类型的类型别名中,属性是用分号 (;) 还是逗号 (,) 分隔的?

Typescript 编译错误:TS2339:类型string上不存在属性props