diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..84a8253 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,18 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mysql" + url = "mysql://root:233456@localhost:8889/paul_api_next" +} + +model ACGM { + id Int @id @default(autoincrement()) + title String? + artist String? + album String? + music_id Int @unique + created_at DateTime @default(now()) + updated_at DateTime? +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..da8dafe --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,25 @@ +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient(); + +async function main() { + console.log(`Start seeding ...`); + + const user = await prisma.aCGM.create({ + data: { + title: "Colorful world", + music_id: 435289270 + }, + }); + + console.log(`Seeding finished.`); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/src/api/acgm.ts b/src/api/acgm.ts new file mode 100644 index 0000000..504b65d --- /dev/null +++ b/src/api/acgm.ts @@ -0,0 +1,94 @@ +import { Api, Get, Query, useContext } from "@midwayjs/hooks"; + +import { getSong } from "../utils/netease"; + +import { client } from "../utils/redis"; +import { prisma } from "../utils/prisma"; +import { ACGM, Prisma } from "@prisma/client"; + +export default Api( + Get(), + Query<{ play?: string }>(), + async () => { + const ctx = useContext(); + + // 增加使用数量 + await client.incr("api-next:stat:acgm"); + + const item = await prisma.$queryRaw( + Prisma.sql`SELECT * FROM ACGM ORDER BY RAND() LIMIT 1` + ) + + console.log(item); + + const id = item ?. [0] ?. music_id; + + if (!id) { + return { + code: 0, + msg: "Failed, no data found" + } + } + + if ("play" in ctx.query) { + ctx.status = 302; + ctx.set("location", `https://music.163.com/song/media/outer/url?id=${id}`); + + return ""; + } + + // 尝试使用缓存 + const cached = await client.lRange(`api-next:163:${id}`, 0, 7); + + if (cached.length) { + return { + code: 1, + msg: "Success", + data: { + id: Number(id), + title: cached[0], + artist: cached[1], + album: cached[2], + cover: cached[3], + lyric: cached[4], + sub_lyric: cached[5], + link: cached[6], + served: Boolean(cached[7]), + cached: true + } + }; + } + + // 全新获取 + const song = await getSong(id); + + if (song) { + await client.rPush(`api-next:163:${id}`, [ + song.title, + song.artist, + song.album, + song.cover, + song.lyric, + song.sub_lyric, + song.link, + song.served + ]); + await client.expire(`api-next:163:${id}`, 21600); + + return { + code: 1, + msg: "Success", + data: { + ...song, + cached: false + } + }; + } + + return { + code: 1, + msg: "Failed", + data: undefined + }; + } +); diff --git a/src/api/bing.ts b/src/api/bing.ts index 2f84811..1a8e8e1 100644 --- a/src/api/bing.ts +++ b/src/api/bing.ts @@ -52,6 +52,8 @@ export default Api( if (!("info" in ctx.query)) { ctx.status = 302; ctx.set("location", image.url); + + return ""; } return { diff --git a/src/api/netease.ts b/src/api/netease.ts new file mode 100644 index 0000000..b38e383 --- /dev/null +++ b/src/api/netease.ts @@ -0,0 +1,72 @@ +import { Api, Get, Query, useContext } from '@midwayjs/hooks'; + +import { getSong } from "../utils/netease"; + +import { client } from '../utils/redis'; + +export default Api( + Get(), + Query<{ id?: string }>(), + async () => { + const ctx = useContext(); + + // 增加使用数量 + await client.incr("api-next:stat:netease"); + + const { id } = ctx.query; + + // 尝试使用缓存 + const cached = await client.lRange(`api-next:163:${id}`, 0, 7); + + if (cached.length) { + return { + code: 1, + msg: "Success", + data: { + id: Number(id), + title: cached[0], + artist: cached[1], + album: cached[2], + cover: cached[3], + lyric: cached[4], + sub_lyric: cached[5], + link: cached[6], + served: Boolean(cached[7]), + cached: true + } + }; + } + + // 全新获取 + const song = await getSong(id); + + if (song) { + await client.rPush(`api-next:163:${id}`, [ + song.title, + song.artist, + song.album, + song.cover, + song.lyric, + song.sub_lyric, + song.link, + song.served + ]); + await client.expire(`api-next:163:${id}`, 21600); + + return { + code: 1, + msg: "Success", + data: { + ...song, + cached: false + } + }; + } + + return { + code: 1, + msg: "Failed", + data: undefined + }; + } +); diff --git a/src/api/stat.ts b/src/api/stat.ts index 42831f4..3320a3f 100644 --- a/src/api/stat.ts +++ b/src/api/stat.ts @@ -1,20 +1,21 @@ -import { Api, Get } from '@midwayjs/hooks'; +import { Api, Get } from "@midwayjs/hooks"; -import { client } from '../utils/redis'; +import { client } from "../utils/redis"; export default Api( Get(), async () => { + const wallpaperStat = await client.get("api-next:stat:wallpaper"); const neteaseStat = await client.get("api-next:stat:netease"); const bingStat = await client.get("api-next:stat:bing"); + const acgmStat = await client.get("api-next:stat:acgm"); return { code: 1, data: { - wallpaper: -1, + wallpaper: Number(wallpaperStat), netease: Number(neteaseStat), - music: -1, - avatar: -1, + acgm: Number(acgmStat), bili: -1, bing: Number(bingStat), } diff --git a/src/utils/netease.ts b/src/utils/netease.ts new file mode 100644 index 0000000..f671052 --- /dev/null +++ b/src/utils/netease.ts @@ -0,0 +1,56 @@ +import fetch from "isomorphic-unfetch"; + +export const getSong = async (id: number) => { + var urlencoded = new URLSearchParams(); + urlencoded.append("ids", `['${id}']`); + + return fetch("http://music.163.com/api/song/detail/", { + method: "POST", + body: urlencoded, + headers: { + "Content-Type": "application/x-www-form-urlencoded", + "Accept-language": "en", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36" + } + }).then(res => { + try{ + return res.json(); + } + catch(e){ + // Todo: 这个地方可能会有问题 + return undefined; + } + }).then(json => { + return json ? parseSongData(json.songs[0]) : undefined; + }) +} + +export const getLyric = async (id: number) => { + return fetch(`http://music.163.com/api/song/lyric?id=${id}&lv=-1&kv=-1&tv=-1`).then(res => { + try{ + return res.json(); + } + catch(e){ + // Todo: 这个地方可能会有问题 + return undefined; + } + }).then(json => { + return json ? [json.lrc ?. lyric, json.tlyric ?. lyric || ""] : ["", ""]; + }) +} + +export const parseSongData = async (item: any) => { + const [lyric, sub_lyric] = await getLyric(item.id); + + return { + id: item.id, + title: item.name, + artist: item.artists ?. [0].name || "", + album: item.album.name || "", + cover: `${item.album.picUrl.replace("http://", "https://")}?param=250y250"`, + lyric, + sub_lyric, + link: `https://music.163.com/song/media/outer/url?id=${item.id}`, + served: false + } +} diff --git a/src/utils/prisma.ts b/src/utils/prisma.ts new file mode 100644 index 0000000..901f3a0 --- /dev/null +++ b/src/utils/prisma.ts @@ -0,0 +1,3 @@ +import { PrismaClient } from "@prisma/client"; + +export const prisma = new PrismaClient(); diff --git a/yarn.lock b/yarn.lock index 42d950f..e54c4d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -629,6 +629,23 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@prisma/client@^3.12.0": + version "3.12.0" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.12.0.tgz#a0eb49ffea5c128dd11dffb896d7139a60073d12" + integrity sha512-4NEQjUcWja/NVBvfuDFscWSk1/rXg3+wj+TSkqXCb1tKlx/bsUE00rxsvOvGg7VZ6lw1JFpGkwjwmsOIc4zvQw== + dependencies: + "@prisma/engines-version" "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" + +"@prisma/engines-version@3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980": + version "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz#829ca3d9d0d92555f44644606d4edfd45b2f5886" + integrity sha512-o+jo8d7ZEiVpcpNWUDh3fj2uPQpBxl79XE9ih9nkogJbhw6P33274SHnqheedZ7PyvPIK/mvU8MLNYgetgXPYw== + +"@prisma/engines@3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980": + version "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980.tgz#e52e364084c4d05278f62768047b788665e64a45" + integrity sha512-zULjkN8yhzS7B3yeEz4aIym4E2w1ChrV12i14pht3ePFufvsAvBSoZ+tuXMvfSoNTgBS5E4bolRzLbMmbwkkMQ== + "@rollup/pluginutils@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.0.tgz#a14bbd058fdbba0a5647143b16ed0d86fb60bd08" @@ -1860,6 +1877,13 @@ hexoid@1.0.0: resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== +history@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" + integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== + dependencies: + "@babel/runtime" "^7.7.6" + http-assert@^1.3.0: version "1.5.0" resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.5.0.tgz#c389ccd87ac16ed2dfa6246fd73b926aa00e6b8f" @@ -2383,6 +2407,13 @@ postcss@^8.4.12: picocolors "^1.0.0" source-map-js "^1.0.2" +prisma@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.12.0.tgz#9675e0e72407122759d3eadcb6d27cdccd3497bd" + integrity sha512-ltCMZAx1i0i9xuPM692Srj8McC665h6E5RqJom999sjtVSccHSD8Z+HSdBN2183h9PJKvC5dapkn78dd0NWMBg== + dependencies: + "@prisma/engines" "3.12.0-37.22b822189f46ef0dc5c5b503368d1bee01213980" + proper-url-join@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/proper-url-join/-/proper-url-join-2.1.1.tgz#ee4146edc08f512a0ee1053a355e06950691d06f" @@ -2441,6 +2472,21 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== +react-router-dom@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d" + integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw== + dependencies: + history "^5.2.0" + react-router "6.3.0" + +react-router@6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" + integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== + dependencies: + history "^5.2.0" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"