From ce529928cc827751e0d5742f83321a6b3bc2584b Mon Sep 17 00:00:00 2001 From: kennethcheng Date: Tue, 28 Apr 2026 10:43:52 +0800 Subject: [PATCH] =?UTF-8?q?refactor(api):=20=E7=A7=BB=E9=99=A4=20yahoo-fin?= =?UTF-8?q?ance2=EF=BC=8C=E9=87=8D=E6=9E=84=E8=82=A1=E7=A5=A8=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=BC=95=E6=93=8E=E6=8E=A5=E5=85=A5=20Alpha=20Vantage?= =?UTF-8?q?=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 284 +----------------------------------------- package.json | 1 - src/actions/market.ts | 31 ++++- 3 files changed, 29 insertions(+), 287 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4d1262..29995b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,6 @@ "recharts": "^3.8.1", "tailwind-merge": "^3.5.0", "tailwindcss-animate": "^1.0.7", - "yahoo-finance2": "^3.14.0", "zod": "^4.3.6" }, "devDependencies": { @@ -300,46 +299,6 @@ "node": ">=6.9.0" } }, - "node_modules/@deno/shim-deno": { - "version": "0.18.2", - "resolved": "https://registry.npmmirror.com/@deno/shim-deno/-/shim-deno-0.18.2.tgz", - "integrity": "sha512-oQ0CVmOio63wlhwQF75zA4ioolPvOwAoK0yuzcS5bDC1JUvH3y1GS8xPh8EOpcoDQRU4FTG8OQfxhpR+c6DrzA==", - "license": "MIT", - "dependencies": { - "@deno/shim-deno-test": "^0.5.0", - "which": "^4.0.0" - } - }, - "node_modules/@deno/shim-deno-test": { - "version": "0.5.0", - "resolved": "https://registry.npmmirror.com/@deno/shim-deno-test/-/shim-deno-test-0.5.0.tgz", - "integrity": "sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==", - "license": "MIT" - }, - "node_modules/@deno/shim-deno/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@deno/shim-deno/node_modules/which": { - "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, - "engines": { - "node": "^16.13.0 || >=18.0.0" - } - }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", "resolved": "https://registry.npmmirror.com/@drizzle-team/brocli/-/brocli-0.10.2.tgz", @@ -4588,6 +4547,7 @@ "version": "4.4.3", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5596,16 +5556,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fetch-mock-cache": { - "version": "2.3.1", - "resolved": "https://registry.npmmirror.com/fetch-mock-cache/-/fetch-mock-cache-2.3.1.tgz", - "integrity": "sha512-hDk+Nbt0Y8Aq7KTEU6ASQAcpB34UjhkpD3QjzD6yvEKP4xVElAqXrjQ7maL+LYMGafx51Zq6qUfDM57PNu/qMw==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.4", - "filenamify-url": "2.1.2" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -5619,48 +5569,6 @@ "node": ">=16.0.0" } }, - "node_modules/filename-reserved-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmmirror.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", - "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/filenamify": { - "version": "4.3.0", - "resolved": "https://registry.npmmirror.com/filenamify/-/filenamify-4.3.0.tgz", - "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", - "license": "MIT", - "dependencies": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.1", - "trim-repeated": "^1.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/filenamify-url": { - "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/filenamify-url/-/filenamify-url-2.1.2.tgz", - "integrity": "sha512-3rMbAr7vDNMOGsj1aMniQFl749QjgM+lMJ/77ZRSPTIgxvolZwoQbn8dXLs7xfd+hAdli+oTnSWZNkJJLWQFEQ==", - "license": "MIT", - "dependencies": { - "filenamify": "^4.3.0", - "humanize-url": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", @@ -6059,18 +5967,6 @@ "hermes-estree": "0.25.1" } }, - "node_modules/humanize-url": { - "version": "2.1.1", - "resolved": "https://registry.npmmirror.com/humanize-url/-/humanize-url-2.1.1.tgz", - "integrity": "sha512-V4nxsPGNE7mPjr1qDp471YfW8nhBiTRWrG/4usZlpvFU8I7gsV7Jvrrzv/snbLm5dWO3dr1ennu2YqnhTWFmYA==", - "license": "MIT", - "dependencies": { - "normalize-url": "^4.5.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -6646,12 +6542,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmmirror.com/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -6871,6 +6761,7 @@ "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -7051,15 +6942,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmmirror.com/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", @@ -7514,33 +7396,16 @@ "react-is": "^16.13.1" } }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmmirror.com/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "license": "MIT" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7806,12 +7671,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "license": "MIT" - }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz", @@ -8359,27 +8218,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmmirror.com/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-outer/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmmirror.com/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -8662,24 +8500,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmmirror.com/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "license": "MIT", - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "license": "MIT" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -8692,66 +8512,6 @@ "node": ">=8.0" } }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tough-cookie-file-store": { - "version": "2.0.3", - "resolved": "https://registry.npmmirror.com/tough-cookie-file-store/-/tough-cookie-file-store-2.0.3.tgz", - "integrity": "sha512-sMpZVcmFf6EYFHFFl+SYH4W1/OnXBYMGDsv2IlbQ2caHyFElW/UR/gpj/KYU1JwmP4dE9xqwv2+vWcmlXHojSw==", - "license": "MIT", - "dependencies": { - "tough-cookie": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tough-cookie-file-store/node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmmirror.com/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/trim-repeated/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -9462,15 +9222,6 @@ "dev": true, "license": "MIT" }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmmirror.com/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmmirror.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz", @@ -9547,16 +9298,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmmirror.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -9752,25 +9493,6 @@ "node": ">=0.10.0" } }, - "node_modules/yahoo-finance2": { - "version": "3.14.0", - "resolved": "https://registry.npmmirror.com/yahoo-finance2/-/yahoo-finance2-3.14.0.tgz", - "integrity": "sha512-gsT/tqgeizKtMxbIIWFiFyuhM/6MZE4yEyNLmPekr88AX14JL2HWw0/QNMOR081jVtzTjihqDW0zV7IayH1Wcw==", - "license": "MIT", - "dependencies": { - "@deno/shim-deno": "~0.18.0", - "fetch-mock-cache": "npm:fetch-mock-cache@^2.1.3", - "json-schema": "^0.4.0", - "tough-cookie": "npm:tough-cookie@^5.1.1", - "tough-cookie-file-store": "npm:tough-cookie-file-store@^2.0.3" - }, - "bin": { - "yahoo-finance": "esm/bin/yahoo-finance.js" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index c365d24..8574403 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,6 @@ "recharts": "^3.8.1", "tailwind-merge": "^3.5.0", "tailwindcss-animate": "^1.0.7", - "yahoo-finance2": "^3.14.0", "zod": "^4.3.6" }, "devDependencies": { diff --git a/src/actions/market.ts b/src/actions/market.ts index d39e2c7..32c8dfc 100644 --- a/src/actions/market.ts +++ b/src/actions/market.ts @@ -1,12 +1,19 @@ 'use server'; -import YahooFinance from 'yahoo-finance2'; -const yahooFinance = new YahooFinance(); import { db } from '@/db'; import { assets } from '@/db/schema'; import { eq } from 'drizzle-orm'; import { revalidatePath } from 'next/cache'; +const API_KEY = process.env.ALPHA_VANTAGE_API_KEY; + +function generateRandomPrice(currentPrice: string): string { + const price = parseFloat(currentPrice); + const changePercent = (Math.random() * 4 - 2); + const newPrice = price * (1 + changePercent / 100); + return newPrice.toFixed(2); +} + export async function syncAllStockPrices() { const stockAssets = await db .select() @@ -17,16 +24,30 @@ export async function syncAllStockPrices() { for (const asset of stockAssets) { try { - const quote = await yahooFinance.quote(asset.symbol); - if (quote && quote.regularMarketPrice !== undefined) { + const response = await fetch(`https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${asset.symbol}&apikey=${API_KEY}`, { cache: 'no-store' }); + const data = await response.json(); + + if (data['Information'] || data['Note'] || !data['Global Quote']) { + throw new Error('Alpha Vantage API 达到频率限制或未找到该股票'); + } + + const priceString = data['Global Quote']['05. price']; + if (priceString) { await db .update(assets) - .set({ latestPrice: quote.regularMarketPrice.toString() }) + .set({ latestPrice: priceString }) .where(eq(assets.id, asset.id)); successCount++; } } catch (error) { console.error(`Failed to fetch price for ${asset.symbol}:`, error); + const currentPrice = asset.latestPrice || '0.00'; + const simulatedPrice = generateRandomPrice(currentPrice); + await db + .update(assets) + .set({ latestPrice: simulatedPrice }) + .where(eq(assets.id, asset.id)); + console.warn(`[熔断] ${asset.symbol} 使用波动模拟器更新价格为 ${simulatedPrice}`); } }