码桶
发现社区成员的开源项目
data.js3.3 KB
/**
* Vercel Serverless Function - Upstash Redis 数据读写
* GET /api/data → 公开读取域名列表 + 网站设置
* POST /api/data → 需认证,保存数据到 Redis
*
* 环境变量(通过 Vercel 安装 Upstash 后自动注入):
* KV_REST_API_URL / KV_REST_API_TOKEN
* 或 UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN
*/
let redis = null;
try {
const { Redis } = require('@upstash/redis');
const url = process.env.UPSTASH_REDIS_REST_URL || process.env.KV_REST_API_URL;
const token = process.env.UPSTASH_REDIS_REST_TOKEN || process.env.KV_REST_API_TOKEN;
if (url && token) {
redis = new Redis({ url, token });
}
} catch (e) {
// 本地开发时包可能未安装
}
// 认证检查(与 auth.js 的 token 格式一致)
function checkAuth(req) {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) return false;
const token = auth.slice(7);
const adminPassword = process.env.ADMIN_PASSWORD || '';
if (!adminPassword) return false;
// 当天 token
const today = new Date().toISOString().slice(0, 10);
const expectedToken = Buffer.from(`${adminPassword}:${today}`).toString('base64');
if (token === expectedToken) return true;
// 昨天的 token(跨午夜宽限)
const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10);
const yToken = Buffer.from(`${adminPassword}:${yesterday}`).toString('base64');
return token === yToken;
}
module.exports = async (req, res) => {
// CORS
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
// 检查 Redis 是否可用
if (!redis) {
return res.status(503).json({ error: 'REDIS_NOT_CONFIGURED' });
}
// GET — 公开读取(前台访客可调用)
if (req.method === 'GET') {
try {
const [domains, config] = await Promise.all([
redis.get('domains'),
redis.get('config')
]);
return res.status(200).json({
domains: domains !== null ? domains : null,
config: config !== null ? config : null
});
} catch (e) {
console.error('Redis GET error:', e);
return res.status(500).json({ error: e.message });
}
}
// POST — 需认证,保存数据
if (req.method === 'POST') {
if (!checkAuth(req)) {
return res.status(401).json({ error: 'Unauthorized' });
}
let body = req.body;
if (typeof body === 'string') {
try {
body = JSON.parse(body);
} catch (e) {
return res.status(400).json({ error: 'Invalid JSON' });
}
}
const { key, value } = body;
if (!key || !['domains', 'config'].includes(key)) {
return res.status(400).json({ error: 'Invalid key, must be "domains" or "config"' });
}
try {
await redis.set(key, value);
return res.status(200).json({ success: true });
} catch (e) {
console.error('Redis SET error:', e);
return res.status(500).json({ error: e.message });
}
}
return res.status(405).json({ error: 'Method Not Allowed' });
};