抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

ContangoAI

AI-Driven Strategies for Financial Markets

最近把一个紫微斗数排盘项目fork下来部署到了Cloudflare Pages上。这个项目排盘、真太阳时、流年、合盘这些功能都比较齐全,唯一的问题是AI解读需要访客自己去模型平台注册充值,然后填写API Key才能使用。对只想算一卦的普通用户来说,这个门槛太高了。

所以我打算配置一个默认的DeepSeek API Key,让访客不填Key也能直接使用AI解读。但项目是纯前端应用,AI请求由浏览器直接发往api.deepseek.com,这意味着Key要么打包进JS要么存在localStorage,只要Key在浏览器里,就没有任何保密可言。这篇文章记录一下我的处理方案。

为什么不能把Key放在环境变量里

首先踩过的坑是VITE_前缀的环境变量。在Cloudflare Pages中,以VITE_开头的变量会在构建时被Vite内联进前端JS包。部署后任何访客都可以在浏览器DevTools的Sources里搜索打包产物,直接提取到完整的Key:

1
grep -o 'sk-[A-Za-z0-9]\{6,\}' dist/assets/*.js

⚠️ 不要用VITE_前缀的变量存放任何Key,它本质上是一个公开变量。

更深一层的问题是架构:项目原本是浏览器直连api.deepseek.com。只要保持直连,Key就必须存在于浏览器里,保密就是伪命题。所以要让默认Key不暴露,只能让浏览器不直接调用DeepSeek,改走自己的服务端。

方案:用Cloudflare Pages Functions做代理

Cloudflare Pages自带服务端运行时Pages Functions。在仓库的app目录下放一个functions/api/chat.ts,部署后会自动变成POST /api/chat接口,和静态站在同一个域名。

函数做的事情很简单:接收前端传来的messages,读取服务端环境变量DEEPSEEK_API_KEY,加上Authorization头转发给api.deepseek.com,再把响应返回给浏览器。Key只出现在服务端,浏览器全程看不到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export async function onRequestPost(context) {
const apiKey = context.env.DEEPSEEK_API_KEY
if (!apiKey) {
return json(503, '站点暂未配置默认 AI 额度,请自行填写 Key')
}

return fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify(upstreamBody),
})
}

在Cloudflare Dashboard的Pages项目 → Settings → Variables and Secrets里添加DEEPSEEK_API_KEY,类型选择Secret。部署完成后检查构建产物,应该找不到任何Key相关的字符串:

1
grep -rl 'DEEPSEEK_API_KEY' dist/

前端路由逻辑

前端在llm.ts里加了一个判断函数,只有同时满足”选择DeepSeek厂商”和”没有填写自己的API Key”两个条件才走共享代理:

1
2
3
export function isSharedDefaultActive(provider, apiKey) {
return provider === 'deepseek' && !apiKey && SHARED_DEEPSEEK_ENABLED
}

具体规则:

  • 没填Key的DeepSeek用户:请求发往/api/chat,使用站点提供的默认Key;
  • 填了自己Key的用户:仍然直连DeepSeek,Key只在用户浏览器和DeepSeek之间传输,不经过我的服务器;
  • 其他厂商(Kimi、Gemini、Claude):保持原有逻辑不变。

另外我把默认厂商从kimi改成了deepseek。这样新访客打开网页,排完盘直接就能用AI解读,不需要任何配置。老访客的localStorage里保存的还是原来的kimi配置,不会被覆盖。

如果部署方不想开放共享额度,可以用构建变量VITE_SHARED_DEEPSEEK=false关闭这个功能。

防止共享Key被恶意刷

共享Key放在公共接口后面,任何人都能调用,恶意脚本可以消耗DeepSeek的余额。所以函数里加了一些限制:

限制 说明
请求格式 只接受POST + application/json
messages数量 最多10条
总字符数 不超过20k
max_tokens 普通模式封顶4096,思考模式封顶8000
来源白名单 可选,配置ALLOWED_ORIGINS后只允许指定来源调用
按IP限流 可选,绑定KV命名空间RATE_LIMIT_KV后每IP每窗口最多10次

max_tokens封顶用于限制单次请求的消耗,按IP限流用于限制同一个IP的调用频率。另外建议在DeepSeek控制台开启余额预警,一旦余额异常可以及时发现。

这些措施能降低被刷的风险,但无法完全杜绝。如果对安全要求更高,可以在Cloudflare控制台配置WAF规则或更严格的限流策略。

其他改动

另外把AI解读正文的字体从毛笔楷体换成了默认的无衬线体。书法体显示短句效果不错,但长文本阅读比较吃力。涉及解读正文、年度运势、合盘分析、K线浮层和分享卡片金句共七处,一并统一。

总结

  • 静态站没有秘密,浏览器能使用的Key都能被提取,VITE_前缀的变量尤其明显。
  • 共享Key的正确做法是放在服务端,浏览器只请求自己的代理接口。
  • 共享Key上线后要做好限流和封顶,防止余额被恶意消耗。
  • 这套方案的额外成本很低,一次完整AI解读大约消耗2k token,DeepSeek计费大概是一分钱级别。

参考

评论



Powered by Hexo | Theme keep Volantis

本站总访问量 总访客数 🌎