This commit is contained in:
Sakurasan
2026-07-05 21:29:17 +08:00
commit 013ebdaffe
150 changed files with 15614 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
// 股票数据代理 Edge Function
// 通过腾讯财经API获取A股实时行情
function corsHeaders() {
return {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'content-type',
};
}
// 从腾讯返回的文本中提取股票数据
function parseTencentData(text: string): string[] | null {
// 腾讯格式:v_sh600519="1~贵州茅台~...";
// 找到第一个 = 后的 " 到最后一个 " 之间的内容
const eqIdx = text.indexOf('="');
if (eqIdx === -1) return null;
const startIdx = eqIdx + 2;
// 从末尾找最后一个 "
const endIdx = text.lastIndexOf('"');
if (endIdx <= startIdx) return null;
const content = text.substring(startIdx, endIdx);
if (!content) return null;
return content.split('~');
}
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders() });
}
const url = new URL(req.url);
const code = url.searchParams.get('code') || '';
if (!/^\d{6}$/.test(code)) {
return new Response(JSON.stringify({ error: '股票代码格式错误,需为6位数字' }), {
status: 400,
headers: corsHeaders(),
});
}
// 市场标识:
// - 688/60开头:上海(sh,含科创板)
// - 920/8/4开头:北交所(bj
// - 其他:深圳(sz
let market: string;
if (code.startsWith('688') || code.startsWith('60')) {
market = 'sh';
} else if (code.startsWith('920') || code.startsWith('8') || code.startsWith('4')) {
market = 'bj';
} else {
market = 'sz';
}
const stockCode = `${market}${code}`;
try {
const resp = await fetch(`https://qt.gtimg.cn/q=${stockCode}`, {
headers: { 'User-Agent': 'Mozilla/5.0' },
});
if (!resp.ok) {
return new Response(JSON.stringify({ error: `上游响应异常: ${resp.status}` }), {
status: 502,
headers: corsHeaders(),
});
}
const buffer = await resp.arrayBuffer();
let text: string;
try {
text = new TextDecoder('gbk').decode(buffer);
} catch {
text = new TextDecoder().decode(buffer);
}
const parts = parseTencentData(text);
if (!parts || parts.length < 30) {
return new Response(JSON.stringify({
error: '数据解析失败',
preview: text.substring(0, 200),
}), {
status: 500,
headers: corsHeaders(),
});
}
// 腾讯行情字段说明(基于实际数据验证)
// 1:名称 2:代码 3:当前价 4:昨收 5:今开 6:成交量(手)
// 31:涨跌额 32:涨跌幅 33:最高 34:最低 37:成交额(万)
const name = parts[1] || '';
const currentPrice = parseFloat(parts[3]) || 0;
const yesterdayClose = parseFloat(parts[4]) || 0;
const todayOpen = parseFloat(parts[5]) || 0;
const volume = parseFloat(parts[6]) || 0;
const high = parseFloat(parts[33]) || 0;
const low = parseFloat(parts[34]) || 0;
const amount = parseFloat(parts[37]) || 0;
const changeFromData = parseFloat(parts[31]) || 0;
const changePercentFromData = parseFloat(parts[32]) || 0;
// 外盘(索引7)和内盘(索引8)数据,用于计算净主动买入额
const outerDisk = parseFloat(parts[7]) || 0;
const innerDisk = parseFloat(parts[8]) || 0;
if (!name || currentPrice === 0) {
return new Response(JSON.stringify({
error: '股票不存在或已停牌',
name,
currentPrice,
}), {
status: 404,
headers: corsHeaders(),
});
}
// 优先使用腾讯返回的涨跌额/涨跌幅
const change = changeFromData !== 0 ? changeFromData : (currentPrice - yesterdayClose);
const changePercent = changePercentFromData !== 0
? changePercentFromData
: (yesterdayClose > 0 ? ((currentPrice - yesterdayClose) / yesterdayClose) * 100 : 0);
const result = {
code,
market: market.toUpperCase(),
name,
todayOpen,
yesterdayClose,
currentPrice,
high,
low,
volume: volume * 100,
amount,
outerDisk,
innerDisk,
date: new Date().toISOString().split('T')[0],
time: new Date().toTimeString().split(' ')[0],
change: parseFloat(change.toFixed(2)),
changePercent: parseFloat(changePercent.toFixed(2)),
};
return new Response(JSON.stringify({ data: result }), {
headers: corsHeaders(),
});
} catch (error) {
const msg = error instanceof Error ? error.message : String(error);
return new Response(JSON.stringify({ error: `获取数据失败: ${msg}` }), {
status: 500,
headers: corsHeaders(),
});
}
});