fix(api): 升级行情解析器正则规则,兼容 BRK.B 等包含特殊字符的股票代码

This commit is contained in:
kennethcheng 2026-05-01 23:35:31 +08:00
parent f059aeb08f
commit 9ff48f37d1
3 changed files with 10 additions and 4 deletions

View File

@ -89,6 +89,12 @@
## 修复记录 ## 修复记录
## 修复行情解析引擎的正则匹配规则,增加对 [.\-] 等特殊字符的支持,解决 BRK.B 等特殊股票代码解析失败导致现价归零的 Bug (Task 62)
- 修复了 `src/actions/market.ts``getTencentSymbol()` 函数的 `cleanSymbol` 正则过滤逻辑:将 `/[^0-9A-Z]/g` 升级为 `/[^0-9A-Z.\-]/g`,保留小数点 `.` 和连字符 `-`
- 修复了 `app/api/cron/fetch-prices/route.ts``fetchStockPrice()` 函数的同名正则过滤逻辑,保持一致。
- **根因:** 旧正则 `/[^0-9A-Z]/g` 会错误地将 `BRK.B` 过滤为 `BRKB`,导致腾讯行情 API 无法识别该股票代码,返回空数据或错误数据,最终使 Dashboard 显示 `$0.00`
- **验证:** 使用 `curl` 调用 `https://sqt.gtimg.cn/q=s_usBRK.B` 成功返回 `BRK.B` 现价 `$476.92`,确认修复生效。
## 修复 Drizzle ORM 的逻辑或语法错误,将错误的链式 .or() 改写为更具扩展性的 inArray() 语法 ## 修复 Drizzle ORM 的逻辑或语法错误,将错误的链式 .or() 改写为更具扩展性的 inArray() 语法
- 修复 `app/api/cron/fetch-prices/route.ts``.where(eq(assets.type, 'STOCK').or(eq(assets.type, 'CRYPTO')))` 的非法链式调用语法。 - 修复 `app/api/cron/fetch-prices/route.ts``.where(eq(assets.type, 'STOCK').or(eq(assets.type, 'CRYPTO')))` 的非法链式调用语法。
- 废弃错误的 `.where(eq(...).or(eq(...)))` 模式,改为使用 `inArray(assets.type, ['STOCK', 'CRYPTO'])` - 废弃错误的 `.where(eq(...).or(eq(...)))` 模式,改为使用 `inArray(assets.type, ['STOCK', 'CRYPTO'])`

View File

@ -31,7 +31,7 @@ function parseMarketDate(rawString: string): string {
} }
async function fetchStockPrice(asset: { symbol: string; exchange: string | null }): Promise<{ price: string | null; rawResponse: string | null }> { async function fetchStockPrice(asset: { symbol: string; exchange: string | null }): Promise<{ price: string | null; rawResponse: string | null }> {
const cleanSymbol = asset.symbol.trim().toUpperCase().replace(/[^0-9A-Z]/g, ''); const cleanSymbol = asset.symbol.trim().toUpperCase().replace(/[^0-9A-Z.\-]/g, '');
let tCode: string; let tCode: string;
switch (asset.exchange) { switch (asset.exchange) {
@ -50,7 +50,7 @@ async function fetchStockPrice(asset: { symbol: string; exchange: string | null
break; break;
} }
const response = await fetch(`https://qt.gtimg.cn/q=${tCode}`, { cache: 'no-store' }); const response = await fetch(`https://sqt.gtimg.cn/q=${tCode}`, { cache: 'no-store' });
const arrayBuffer = await response.arrayBuffer(); const arrayBuffer = await response.arrayBuffer();
const decoder = new TextDecoder('gbk'); const decoder = new TextDecoder('gbk');
const text = decoder.decode(arrayBuffer); const text = decoder.decode(arrayBuffer);

View File

@ -8,7 +8,7 @@ import { revalidatePath } from 'next/cache';
import { onConflictDoUpdate } from 'drizzle-orm/pg-core'; import { onConflictDoUpdate } from 'drizzle-orm/pg-core';
function getTencentSymbol(asset: { symbol: string; exchange: string | null }): string { function getTencentSymbol(asset: { symbol: string; exchange: string | null }): string {
const cleanSymbol = asset.symbol.trim().toUpperCase().replace(/[^0-9A-Z]/g, ''); const cleanSymbol = asset.symbol.trim().toUpperCase().replace(/[^0-9A-Z.\-]/g, '');
switch (asset.exchange) { switch (asset.exchange) {
case 'SSE': return 'sh' + cleanSymbol; case 'SSE': return 'sh' + cleanSymbol;
@ -36,7 +36,7 @@ export async function syncAllMarketPrices() {
try { try {
if (asset.type === 'STOCK') { if (asset.type === 'STOCK') {
const tCode = getTencentSymbol(asset); const tCode = getTencentSymbol(asset);
const response = await fetch(`https://qt.gtimg.cn/q=${tCode}`, { cache: 'no-store' }); const response = await fetch(`https://sqt.gtimg.cn/q=${tCode}`, { cache: 'no-store' });
const arrayBuffer = await response.arrayBuffer(); const arrayBuffer = await response.arrayBuffer();
const decoder = new TextDecoder('gbk'); const decoder = new TextDecoder('gbk');
const text = decoder.decode(arrayBuffer); const text = decoder.decode(arrayBuffer);