diff --git a/.cursor/rules/shensheshou.mdc b/.cursor/rules/shensheshou.mdc index 89f1da6..b5e8a99 100644 --- a/.cursor/rules/shensheshou.mdc +++ b/.cursor/rules/shensheshou.mdc @@ -11,10 +11,11 @@ alwaysApply: true 每次开始对话前,必须先读取以下文档了解项目上下文: ``` -1. 开发文档/00_项目核心文档.md → 数据库结构、AI规则、API规范 -2. 开发文档/01_开发进度文档.md → 当前进度、待办任务、落地规范 -3. 开发文档/02_提示词文档.md → 历史对话提示词、默认规则 +1. 开发文档/10、项目管理/项目核心文档.md → 数据库结构、AI规则、API规范 +2. 开发文档/10、项目管理/开发进度.md → 当前进度、每日进度、待办任务 +3. 开发文档/10、项目管理/提示词存档.md → 历史对话提示词、默认规则 ``` +(开发文档仅保留 1~10 共 10 个目录,入口见 10、项目管理/README.md) ## 二、项目记忆要点 @@ -44,22 +45,20 @@ alwaysApply: true ## 三、对话后更新规范 -每次对话结束后,必须更新以下内容: +每次对话结束后,必须更新以下内容(路径均在 开发文档/10、项目管理/): -1. **开发进度文档** - - 标记完成的任务 ✅ - - 添加完成日期 - - 更新待办任务 +1. **开发进度.md** + - 在顶部「每日开发进度」表增加一行(日期、当日完成、进行中) + - 标记完成的任务 ✅,更新待办任务 -2. **提示词文档** - - 记录用户提示词 - - 记录执行结果 - - 记录时间节点 +2. **提示词存档.md** + - 记录用户提示词、执行结果、时间节点 -3. **项目核心文档** - - 新发现的规则 - - 数据库结构变更 - - API接口更新 +3. **项目核心文档.md**(若有变更) + - 新发现的规则、数据库结构变更、API 接口更新 + +4. **优化与迭代.md**(若有新优化项) + - 已完成事项或待办优化 ## 四、UI设计规范 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..46da4e2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +# 神射手 Docker 构建排除 +node_modules +.next +.git +.env* +*.md +开发文档 +.apm +.cursor +scripts +*.log +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..90c7bf1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# 神射手数据中台 - Docker 镜像(NAS/生产) +# 多阶段:构建 Next.js standalone → 运行 + +FROM node:20-alpine AS builder + +WORKDIR /app + +# 依赖:仅复制 package.json,在 Linux 下安装以得到正确的 @next/swc-linux-*(避免 darwin 专用包) +COPY package.json ./ +RUN npm install + +COPY . . +RUN (pnpm run build 2>/dev/null || npm run build) + +# 运行阶段:仅保留 standalone 输出 +FROM node:20-alpine AS runner + +WORKDIR /app + +ENV NODE_ENV=production +ENV PORT=3117 +EXPOSE 3117 + +# 从构建阶段复制 standalone +COPY --from=builder /app/.next/standalone ./ +COPY --from=builder /app/.next/static ./.next/static +COPY --from=builder /app/public ./public + +# 无 root 运行 +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nextjs && \ + chown -R nextjs:nodejs /app +USER nextjs + +CMD ["node", "server.js"] diff --git a/README_website.md b/README_website.md new file mode 100644 index 0000000..49a0285 --- /dev/null +++ b/README_website.md @@ -0,0 +1,37 @@ +# website 编排(玩值电竞 + 神射手) + +- **玩值电竞**:http://localhost:3001 +- **神射手**:http://localhost:3117 + +## 首次使用(必做一次) + +1. **先启动 Docker Desktop** + 从「应用程序」打开 Docker Desktop,等待菜单栏/托盘图标就绪(不再转圈)。 + 若未安装:从 https://www.docker.com/products/docker-desktop 安装。 + +2. **在本目录执行** + ```bash + ./启动website.sh + ``` + 脚本会自动:若 Docker 未运行则尝试打开 Docker Desktop,等待就绪后执行 `docker compose up -d --build`。 + +3. 浏览器打开 **http://localhost:3001** 即可访问玩值电竞。 + +## 之后每次 + +- Docker Desktop 已开:直接 `./启动website.sh` 或 `docker compose up -d --build`。 +- Docker 未开:先打开 Docker Desktop,就绪后再执行上述命令。 + +## 报错「Cannot connect to the Docker daemon」 + +表示 Docker 未运行。请先打开 Docker Desktop 并等待完全启动,再执行 `./启动website.sh`。 + +## 浏览器 localhost:3001 报 ERR_CONNECTION_REFUSED + +可能原因:容器在跑但容器内进程已退出、或端口未正确映射。在本目录执行: + +```bash +./诊断并修复3001.sh +``` + +脚本会:查看容器状态与日志 → 执行 `docker compose down` 再 `up -d --build` → 检查 3001 是否被监听。完成后在浏览器访问 http://localhost:3001。 diff --git a/app/api/ai-chat/route.ts b/app/api/ai-chat/route.ts index 2b5b780..5763139 100644 --- a/app/api/ai-chat/route.ts +++ b/app/api/ai-chat/route.ts @@ -4,7 +4,7 @@ */ import { NextRequest, NextResponse } from "next/server" -import { getMongoClient, intelligentSearch, queryFullProfile, getDatabaseStats } from "@/lib/mongodb" +import { getMongoClient, intelligentSearch, queryFullProfile, getDatabaseStats, unifiedResolveToPhones } from "@/lib/mongodb" // 消息类型 interface ChatMessage { @@ -14,49 +14,67 @@ interface ChatMessage { data?: any } -// 解析用户意图 -function parseIntent(message: string): { type: string; query?: string; params?: any } { - const msg = message.trim().toLowerCase() - - // 查询手机号 - const phoneMatch = message.match(/(?:查|查询|搜索|找)?[::\s]*(\+?86)?1[3-9]\d{9}/g) - if (phoneMatch) { - const phone = phoneMatch[0].replace(/[查询搜索找::\s]/g, '').replace(/^\+?86/, '') - return { type: "query_phone", query: phone } - } - - // 查询 QQ - const qqMatch = message.match(/(?:qq|QQ)[::\s]*(\d{5,11})|(\d{5,11})\s*(?:qq|QQ)/i) - if (qqMatch) { - return { type: "query_qq", query: qqMatch[1] || qqMatch[2] } - } - - // 系统状态 - if (msg.includes("状态") || msg.includes("统计") || msg.includes("总量")) { - return { type: "system_status" } - } - - // RFM 分析 - if (msg.includes("rfm") || msg.includes("估值") || msg.includes("价值")) { - return { type: "rfm_analysis" } - } - - // 高价值用户 - if (msg.includes("高价值") || msg.includes("top") || msg.includes("排行")) { - const limitMatch = msg.match(/(\d+)/) - return { type: "high_value_users", params: { limit: limitMatch ? parseInt(limitMatch[1]) : 10 } } - } - - // 帮助 - if (msg.includes("帮助") || msg.includes("help") || msg === "?") { - return { type: "help" } - } - - // 通用搜索 - return { type: "search", query: message } +// 系统/统计类意图(非单人/多人查询) +function isSystemIntent(msg: string): boolean { + const m = msg.trim().toLowerCase() + return !m || m === '?' || + /状态|统计|总量/.test(m) || + /^rfm|估值|价值$/.test(m) || + /^高价值|top|排行/.test(m) || + /帮助|help/.test(m) } -// 格式化用户数据 +// 将输入拆成多段(支持一人或多人:手机、QQ、身份证、姓名/关键词) +function parseSegments(message: string): { type: 'phone' | 'qq' | 'id_card' | 'keyword'; value: string }[] { + const raw = message.trim() + const parts = raw.split(/[,,、\n]+/).map(s => s.trim()).filter(Boolean) + if (parts.length === 0) return [{ type: 'keyword', value: raw }] + + const segments: { type: 'phone' | 'qq' | 'id_card' | 'keyword'; value: string }[] = [] + for (const p of parts) { + const v = p.replace(/^[查查询找搜索::\s]+/i, '').trim() + if (!v) continue + // 手机号:11 位 1[3-9] 开头 + if (/^(\+?86)?1[3-9]\d{9}$/.test(v.replace(/\s/g, ''))) { + segments.push({ type: 'phone', value: v.replace(/\D/g, '').replace(/^86/, '') }) + continue + } + // QQ:带 qq 前缀或 5~10 位数字(11 位且非 1 开头当 QQ) + const qqMatch = v.match(/(?:qq|QQ)[::\s]*(\d{5,11})|(\d{5,10})\s*(?:qq|QQ)?|(\d{5,11})/i) + if (qqMatch) { + const qq = (qqMatch[1] || qqMatch[2] || qqMatch[3] || '').trim() + if (qq && qq.length >= 5 && qq.length <= 11 && !/^1[3-9]\d{9}$/.test(qq)) { + segments.push({ type: 'qq', value: qq }) + continue + } + } + // 身份证:18 位或 17 位 + X + if (/^\d{17}[\dXx]$/.test(v.replace(/\s/g, ''))) { + segments.push({ type: 'id_card', value: v.replace(/\s/g, '') }) + continue + } + // 姓名/关键词 + segments.push({ type: 'keyword', value: v }) + } + return segments.length ? segments : [{ type: 'keyword', value: raw }] +} + +// 解析用户意图(兼容旧单条逻辑) +function parseIntent(message: string): { type: string; query?: string; params?: any } { + const msg = message.trim().toLowerCase() + if (isSystemIntent(message)) { + if (/状态|统计|总量/.test(msg)) return { type: "system_status" } + if (/rfm|估值|价值/.test(msg)) return { type: "rfm_analysis" } + if (/高价值|top|排行/.test(msg)) { + const limitMatch = message.match(/(\d+)/) + return { type: "high_value_users", params: { limit: limitMatch ? parseInt(limitMatch[1]) : 10 } } + } + if (/帮助|help|\?/.test(msg)) return { type: "help" } + } + return { type: "unified_search", query: message } +} + +// 格式化用户数据(简要) function formatUserData(user: any): string { if (!user) return "未找到用户信息" @@ -78,6 +96,87 @@ function formatUserData(user: any): string { return lines.join('\n') } +// 完整地址(省市区/地区统一) +function formatAddress(fullProfile: { valuation?: any; qqPhone?: any }): string { + const v = fullProfile.valuation + const q = fullProfile.qqPhone + const province = v?.province || q?.省份 || '' + const city = v?.city || q?.地区 || '' + const extra = (v?.district || v?.区县 || (v as any)?.区 || '') + return [province, city, extra].filter(Boolean).join(' ') || '—' +} + +// 统一标签(用户估值 + 存客宝合并去重) +function getAllTags(fullProfile: { valuation?: any; ckbAsset?: any }): string[] { + const tags = new Set() + fullProfile.valuation?.unified_tags?.forEach((t: string) => tags.add(t)) + fullProfile.valuation?.tags?.forEach((t: string) => tags.add(t)) + fullProfile.ckbAsset?.tags?.forEach((t: string) => tags.add(t)) + return Array.from(tags) +} + +// 完整用户画像(含手机、QQ、地址、全部标签,跨库) +function formatFullPortrait(fullProfile: { valuation?: any; qqPhone?: any; ckbAsset?: any }, index?: number): string { + const { valuation, qqPhone, ckbAsset } = fullProfile + const lines: string[] = [] + const title = index != null ? `━━━ 第 ${index + 1} 条 · 完整用户画像 ━━━` : '━━━ 完整用户画像 ━━━' + lines.push(title + '\n') + + if (valuation) { + lines.push('【用户估值】') + if (valuation.name) lines.push(`👤 姓名: ${valuation.name}`) + if (valuation.phone_masked || valuation.phone) lines.push(`📱 手机: ${valuation.phone_masked || valuation.phone}`) + if (valuation.user_evaluation_score != null) lines.push(`⭐ 估值分: ${valuation.user_evaluation_score}`) + if (valuation.user_level) lines.push(`🏆 等级: ${valuation.user_level}`) + lines.push(`📍 地址: ${formatAddress(fullProfile)}`) + const tags = getAllTags(fullProfile) + if (tags.length) lines.push(`🏷️ 统一标签: ${tags.join('、')}`) + if (valuation.traffic_pool?.pool_name) lines.push(`📦 流量池: ${valuation.traffic_pool.pool_name}`) + if (valuation.source_channels?.length) lines.push(`📂 数据来源: ${valuation.source_channels.join(', ')}`) + lines.push('') + } + + if (qqPhone) { + lines.push('【QQ/手机关联】') + lines.push(`💬 QQ: ${qqPhone.qq}`) + if (qqPhone.QQ号评分 != null) lines.push(` QQ评分: ${qqPhone.QQ号评分}`) + if (qqPhone.手机号评分 != null) lines.push(` 手机评分: ${qqPhone.手机号评分}`) + if (qqPhone.运营商) lines.push(`📶 运营商: ${qqPhone.运营商}`) + if (qqPhone.省份 || qqPhone.地区) lines.push(`📍 地区: ${qqPhone.省份 || ''} ${qqPhone.地区 || ''}`) + lines.push('') + } + + if (ckbAsset) { + lines.push('【存客宝资产】') + if (ckbAsset.nickname) lines.push(`昵称: ${ckbAsset.nickname}`) + if (ckbAsset.total_assets != null) lines.push(`资产: ${ckbAsset.total_assets}`) + if (ckbAsset.tags?.length) lines.push(`标签: ${ckbAsset.tags.join(', ')}`) + lines.push('') + } + + return lines.join('\n').trim() +} + +// 根据完整画像生成 AI 分析文案(规则摘要) +function generateAIAnalysis(fullProfile: { valuation?: any; qqPhone?: any; ckbAsset?: any }): string { + const { valuation, qqPhone, ckbAsset } = fullProfile + const parts: string[] = [] + + if (valuation) { + const level = valuation.user_level || '未分级' + const score = valuation.user_evaluation_score ?? 0 + const addr = formatAddress(fullProfile) + parts.push(`该用户为 ${level} 级用户,估值分 ${score},地址:${addr}。`) + const tags = getAllTags(fullProfile) + if (tags.length) parts.push(`统一标签:${tags.join('、')}。`) + if (valuation.traffic_pool?.pool_name) parts.push(`归属流量池「${valuation.traffic_pool.pool_name}」。`) + } + if (qqPhone) parts.push(`已关联 QQ,运营商 ${qqPhone.运营商 || '未知'}。`) + if (ckbAsset) parts.push(`存客宝有资产记录,可做精细化运营。`) + if (parts.length === 0) return '暂无足够数据生成分析。' + return parts.join(' ') +} + // 处理 AI 聊天 async function processChat(message: string): Promise { const startTime = Date.now() @@ -85,59 +184,46 @@ async function processChat(message: string): Promise { try { switch (intent.type) { - case "query_phone": { - const result = await queryFullProfile(intent.query!) - if (result.valuation || result.qqPhone) { - const user = { - ...result.valuation, - qq: result.qqPhone?.qq, - carrier: result.qqPhone?.运营商 - } + case "unified_search": { + const segments = parseSegments(message) + const phones = await unifiedResolveToPhones(segments, 10) + if (phones.length === 0) { return { role: "assistant", - content: `🎯 手机号 ${intent.query} 查询结果:\n\n${formatUserData(user)}\n\n⏱️ 查询耗时: ${Date.now() - startTime}ms`, - data: result + content: `🤔 未找到与「${message}」相关的用户\n\n💡 支持:手机号、QQ、身份证、姓名/城市/省份,可多条用逗号分隔,最多列出前 10 条完整画像。` } - } else { + } + const profiles: { fullProfile: any; aiAnalysis: string }[] = [] + for (const phone of phones) { + const fullProfile = await queryFullProfile(phone) + if (fullProfile.valuation || fullProfile.qqPhone) { + profiles.push({ + fullProfile, + aiAnalysis: generateAIAnalysis(fullProfile) + }) + } + } + if (profiles.length === 0) { return { role: "assistant", - content: `❌ 未找到手机号 ${intent.query} 的相关信息\n\n💡 提示: 请检查手机号是否正确(11位数字)` + content: `❌ 未解析到有效用户画像\n\n⏱️ 耗时: ${Date.now() - startTime}ms` } } + const contentParts: string[] = [ + `🔍 共找到 ${profiles.length} 条用户,完整画像如下(前 10 条)\n` + ] + profiles.forEach((p, i) => { + contentParts.push(formatFullPortrait(p.fullProfile, i)) + contentParts.push(`\n🤖 AI 分析:${p.aiAnalysis}\n`) + }) + contentParts.push(`\n⏱️ 查询耗时: ${Date.now() - startTime}ms`) + return { + role: "assistant", + content: contentParts.join('\n'), + data: { profiles, list: profiles } + } } - - case "query_qq": { - const client = await getMongoClient() - const qqDb = client.db("KR_腾讯") - let qqDoc = await qqDb.collection("QQ+手机").findOne({ qq: intent.query }) - if (!qqDoc) { - qqDoc = await qqDb.collection("QQ+手机").findOne({ qq: parseInt(intent.query!) }) - } - - if (qqDoc) { - const phone = qqDoc.phone || qqDoc.手机号 - let userInfo = `🎯 QQ ${intent.query} 查询结果:\n\n` - userInfo += `💬 QQ: ${qqDoc.qq}\n` - userInfo += `📱 手机: ${phone ? phone.toString().replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '未知'}\n` - userInfo += `📊 QQ评分: ${qqDoc.QQ号评分 || 'N/A'}\n` - userInfo += `📊 手机评分: ${qqDoc.手机号评分 || 'N/A'}\n` - userInfo += `📍 地区: ${qqDoc.省份 || ''}${qqDoc.地区 || ''}\n` - userInfo += `📶 运营商: ${qqDoc.运营商 || 'N/A'}\n` - userInfo += `\n⏱️ 查询耗时: ${Date.now() - startTime}ms` - - return { - role: "assistant", - content: userInfo, - data: qqDoc - } - } else { - return { - role: "assistant", - content: `❌ 未找到 QQ ${intent.query} 的相关信息` - } - } - } - + case "system_status": { const stats = await getDatabaseStats() return { @@ -206,39 +292,18 @@ async function processChat(message: string): Promise { return { role: "assistant", content: `🎯 神射手 AI 助手使用指南\n\n` + - `📱 查询手机号:\n "查 13800138000" 或 "13800138000"\n\n` + - `💬 查询 QQ:\n "28533368 qq" 或 "qq 28533368"\n\n` + - `📊 系统状态:\n "系统状态" 或 "统计"\n\n` + - `📈 RFM 分析:\n "RFM分析" 或 "用户估值"\n\n` + - `🏆 高价值用户:\n "高价值用户 TOP10"\n\n` + - `💡 数据覆盖: 20亿+用户,207GB数据` + `📱 手机 / 💬 QQ / 🪪 身份证 / 👤 姓名:统一搜索,最多列出前 10 条完整画像\n\n` + + `• 支持多条同时查:用逗号或换行分隔,如 "13800138000, 28533368 qq, 张三"\n` + + `• 每条画像含:手机、QQ、地址、统一标签、流量池、存客宝、AI 分析\n\n` + + `📊 系统状态 / 🏆 高价值 / 📈 RFM:输入 "系统状态"、"高价值用户 TOP10"、"RFM分析"\n\n` + + `💡 数据覆盖: 20亿+用户,曼谷库内数据统一展示` } } - case "search": default: { - // 尝试智能搜索 - const result = await intelligentSearch(intent.query || message, { limit: 5 }) - if (result.total > 0) { - let content = `🔍 搜索结果 (共${result.total}条)\n\n` - result.users.slice(0, 5).forEach((u, i) => { - content += `${i + 1}. ${u.name || '未知'} - ${u.city || ''} - 估值: ${u.user_evaluation_score || 'N/A'}\n` - }) - content += `\n⏱️ 查询耗时: ${Date.now() - startTime}ms` - return { - role: "assistant", - content, - data: result - } - } else { - return { - role: "assistant", - content: `🤔 我不太理解 "${message}"\n\n` + - `💡 你可以尝试:\n` + - `- 查询手机号: "查 13800138000"\n` + - `- 查询QQ: "28533368 qq"\n` + - `- 输入 "帮助" 查看更多功能` - } + return { + role: "assistant", + content: `🤔 未识别指令\n\n输入 "帮助" 查看支持:手机、QQ、身份证、姓名,可多条件,前 10 条完整画像。` } } } @@ -299,7 +364,7 @@ export async function GET() { return NextResponse.json({ status: "online", model: "神射手 AI v1.0", - capabilities: ["用户查询", "QQ查询", "RFM分析", "智能搜索"], + capabilities: ["关键字搜索", "完整用户画像", "AI分析", "手机/QQ查询", "RFM分析"], database: { connected: stats.connected, totalUsers: stats.totalDocuments, diff --git a/app/api/api-keys/billing/route.ts b/app/api/api-keys/billing/route.ts new file mode 100644 index 0000000..3396970 --- /dev/null +++ b/app/api/api-keys/billing/route.ts @@ -0,0 +1,209 @@ +import { NextRequest, NextResponse } from 'next/server' + +// 调用日志接口 +interface CallLog { + id: string + keyId: string + keyName: string + endpoint: string + method: string + status: number + credits: number + responseTime: number + timestamp: string + ip: string + requestFields?: string[] +} + +// 计费明细接口 +interface BillingDetail { + category: string + callCount: number + avgPrice: number + totalCredits: number + percentage: number +} + +// 内存存储调用日志 +const callLogs: CallLog[] = [ + { id: 'log_1', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/user', method: 'GET', status: 200, credits: 5, responseTime: 123, timestamp: '2026-01-31 14:32:15', ip: '123.45.67.89' }, + { id: 'log_2', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/users/batch', method: 'POST', status: 200, credits: 40, responseTime: 856, timestamp: '2026-01-31 14:30:02', ip: '123.45.67.89' }, + { id: 'log_3', keyId: 'key_2', keyName: '点了码-测试环境', endpoint: '/api/shensheshou/ai/chat', method: 'POST', status: 200, credits: 5, responseTime: 2341, timestamp: '2026-01-31 14:28:45', ip: '98.76.54.32' }, + { id: 'log_4', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/tags', method: 'GET', status: 200, credits: 0.5, responseTime: 45, timestamp: '2026-01-31 14:25:18', ip: '123.45.67.89' }, + { id: 'log_5', keyId: 'key_2', keyName: '点了码-测试环境', endpoint: '/api/shensheshou/user', method: 'GET', status: 403, credits: 0, responseTime: 12, timestamp: '2026-01-31 14:20:33', ip: '98.76.54.32' }, + { id: 'log_6', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/ai/analyze', method: 'POST', status: 200, credits: 10, responseTime: 5623, timestamp: '2026-01-31 14:15:00', ip: '123.45.67.89' }, + { id: 'log_7', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/packages/create', method: 'POST', status: 200, credits: 5, responseTime: 1234, timestamp: '2026-01-31 14:10:22', ip: '123.45.67.89' }, + { id: 'log_8', keyId: 'key_2', keyName: '点了码-测试环境', endpoint: '/api/shensheshou/ingest', method: 'POST', status: 429, credits: 0, responseTime: 8, timestamp: '2026-01-31 14:05:11', ip: '98.76.54.32' }, +] + +// API分类映射 +const ENDPOINT_CATEGORIES: Record = { + '/api/shensheshou/user': '用户查询', + '/api/shensheshou/users/batch': '用户查询', + '/api/shensheshou/tags': '标签服务', + '/api/shensheshou/tags/apply': '标签服务', + '/api/shensheshou/ai/chat': 'AI服务', + '/api/shensheshou/ai/analyze': 'AI服务', + '/api/shensheshou/ai/tag': 'AI服务', + '/api/shensheshou/sources': '数据服务', + '/api/shensheshou/ingest': '数据服务', + '/api/shensheshou/report/generate': '报告服务', + '/api/shensheshou/packages': '流量包', + '/api/shensheshou/packages/create': '流量包', + '/api/shensheshou/packages/export': '流量包', +} + +// GET: 获取调用日志和计费明细 +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const action = searchParams.get('action') + const keyId = searchParams.get('keyId') + const startDate = searchParams.get('startDate') + const endDate = searchParams.get('endDate') + const page = parseInt(searchParams.get('page') || '1') + const pageSize = parseInt(searchParams.get('pageSize') || '20') + + // 筛选日志 + let filteredLogs = [...callLogs] + + if (keyId) { + filteredLogs = filteredLogs.filter(log => log.keyId === keyId) + } + + if (startDate) { + filteredLogs = filteredLogs.filter(log => log.timestamp >= startDate) + } + + if (endDate) { + filteredLogs = filteredLogs.filter(log => log.timestamp <= endDate) + } + + // 获取计费明细 + if (action === 'billing') { + const categoryStats: Record = {} + let totalCredits = 0 + + filteredLogs.forEach(log => { + const category = ENDPOINT_CATEGORIES[log.endpoint] || '其他' + if (!categoryStats[category]) { + categoryStats[category] = { count: 0, credits: 0 } + } + categoryStats[category].count++ + categoryStats[category].credits += log.credits + totalCredits += log.credits + }) + + const billingDetails: BillingDetail[] = Object.entries(categoryStats).map(([category, stats]) => ({ + category, + callCount: stats.count, + avgPrice: stats.count > 0 ? Number((stats.credits / stats.count).toFixed(2)) : 0, + totalCredits: stats.credits, + percentage: totalCredits > 0 ? Number(((stats.credits / totalCredits) * 100).toFixed(1)) : 0 + })) + + return NextResponse.json({ + success: true, + data: { + details: billingDetails.sort((a, b) => b.totalCredits - a.totalCredits), + totalCredits, + totalCalls: filteredLogs.length, + period: { + start: startDate || filteredLogs[filteredLogs.length - 1]?.timestamp?.split(' ')[0], + end: endDate || filteredLogs[0]?.timestamp?.split(' ')[0] + } + } + }) + } + + // 获取统计概览 + if (action === 'stats') { + const today = new Date().toISOString().split('T')[0] + const todayLogs = filteredLogs.filter(log => log.timestamp.startsWith(today)) + const successLogs = filteredLogs.filter(log => log.status === 200) + + return NextResponse.json({ + success: true, + data: { + today: { + calls: todayLogs.length, + credits: todayLogs.reduce((sum, log) => sum + log.credits, 0), + avgResponseTime: todayLogs.length > 0 + ? Math.round(todayLogs.reduce((sum, log) => sum + log.responseTime, 0) / todayLogs.length) + : 0 + }, + total: { + calls: filteredLogs.length, + credits: filteredLogs.reduce((sum, log) => sum + log.credits, 0), + successRate: filteredLogs.length > 0 + ? Number(((successLogs.length / filteredLogs.length) * 100).toFixed(1)) + : 0 + } + } + }) + } + + // 分页返回日志 + const startIndex = (page - 1) * pageSize + const paginatedLogs = filteredLogs.slice(startIndex, startIndex + pageSize) + + return NextResponse.json({ + success: true, + data: paginatedLogs, + pagination: { + page, + pageSize, + total: filteredLogs.length, + totalPages: Math.ceil(filteredLogs.length / pageSize) + } + }) + + } catch (error) { + console.error('获取计费信息失败:', error) + return NextResponse.json({ + success: false, + error: '获取计费信息失败' + }, { status: 500 }) + } +} + +// POST: 记录API调用 +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const { keyId, keyName, endpoint, method, status, credits, responseTime, ip, requestFields } = body + + const newLog: CallLog = { + id: `log_${Date.now()}`, + keyId, + keyName, + endpoint, + method, + status, + credits: credits || 0, + responseTime: responseTime || 0, + timestamp: new Date().toISOString().replace('T', ' ').substring(0, 19), + ip: ip || 'unknown', + requestFields + } + + callLogs.unshift(newLog) // 添加到开头 + + // 保持日志数量在合理范围 + if (callLogs.length > 10000) { + callLogs.splice(10000) + } + + return NextResponse.json({ + success: true, + data: newLog + }) + + } catch (error) { + console.error('记录调用日志失败:', error) + return NextResponse.json({ + success: false, + error: '记录调用日志失败' + }, { status: 500 }) + } +} diff --git a/app/api/api-keys/route.ts b/app/api/api-keys/route.ts new file mode 100644 index 0000000..536aedd --- /dev/null +++ b/app/api/api-keys/route.ts @@ -0,0 +1,387 @@ +import { NextRequest, NextResponse } from 'next/server' + +// API密钥接口 +interface APIKey { + id: string + name: string + key: string + secret: string + status: 'active' | 'disabled' | 'expired' + createdAt: string + expiresAt: string | null + lastUsed: string | null + permissions: FieldPermission[] + rateLimit: { + requestsPerDay: number + requestsPerMonth: number + } + billing: { + plan: 'free' | 'basic' | 'pro' | 'enterprise' + usedCredits: number + totalCredits: number + } + callStats: { + today: number + thisMonth: number + total: number + } +} + +// 字段权限接口 +interface FieldPermission { + fieldGroup: string + fields: { + name: string + label: string + enabled: boolean + price: number + }[] +} + +// 计费套餐 +const BILLING_PLANS = { + free: { credits: 1000, requestsPerDay: 100, requestsPerMonth: 3000 }, + basic: { credits: 10000, requestsPerDay: 1000, requestsPerMonth: 30000 }, + pro: { credits: 50000, requestsPerDay: 5000, requestsPerMonth: 150000 }, + enterprise: { credits: -1, requestsPerDay: -1, requestsPerMonth: -1 }, +} + +// 默认字段权限 +const DEFAULT_FIELD_PERMISSIONS: FieldPermission[] = [ + { + fieldGroup: '基础信息', + fields: [ + { name: 'phone', label: '手机号', enabled: true, price: 1 }, + { name: 'qq', label: 'QQ号', enabled: true, price: 1 }, + { name: 'wechat', label: '微信号', enabled: false, price: 2 }, + { name: 'email', label: '邮箱', enabled: false, price: 1 }, + { name: 'nickname', label: '昵称', enabled: true, price: 0.5 }, + ] + }, + { + fieldGroup: '用户画像', + fields: [ + { name: 'rfm_score', label: 'RFM评分', enabled: true, price: 2 }, + { name: 'user_level', label: '用户等级', enabled: true, price: 1 }, + { name: 'value_score', label: '价值评分', enabled: false, price: 3 }, + { name: 'activity_score', label: '活跃度评分', enabled: false, price: 2 }, + { name: 'loyalty_score', label: '忠诚度评分', enabled: false, price: 2 }, + ] + }, + { + fieldGroup: '标签数据', + fields: [ + { name: 'basic_tags', label: '基础标签', enabled: true, price: 1 }, + { name: 'behavior_tags', label: '行为标签', enabled: false, price: 2 }, + { name: 'preference_tags', label: '偏好标签', enabled: false, price: 2 }, + { name: 'ai_tags', label: 'AI智能标签', enabled: false, price: 5 }, + { name: 'custom_tags', label: '自定义标签', enabled: true, price: 1 }, + ] + }, + { + fieldGroup: '行为数据', + fields: [ + { name: 'last_active', label: '最后活跃时间', enabled: true, price: 0.5 }, + { name: 'visit_count', label: '访问次数', enabled: false, price: 1 }, + { name: 'purchase_history', label: '购买历史', enabled: false, price: 5 }, + { name: 'interaction_log', label: '交互记录', enabled: false, price: 3 }, + { name: 'channel_source', label: '渠道来源', enabled: true, price: 1 }, + ] + }, + { + fieldGroup: '扩展数据', + fields: [ + { name: 'social_bindings', label: '社交绑定', enabled: false, price: 3 }, + { name: 'device_info', label: '设备信息', enabled: false, price: 2 }, + { name: 'location_data', label: '位置数据', enabled: false, price: 4 }, + { name: 'risk_assessment', label: '风险评估', enabled: false, price: 5 }, + { name: 'ai_insights', label: 'AI洞察', enabled: false, price: 10 }, + ] + }, +] + +// 内存存储(生产环境应使用MongoDB) +let apiKeys: APIKey[] = [ + { + id: 'key_1', + name: '存客宝-生产环境', + key: 'sk-archer-ckb-prod-a1b2c3d4e5f6', + secret: 'sec-ckb-x9y8z7w6v5u4', + status: 'active', + createdAt: '2026-01-15', + expiresAt: null, + lastUsed: '2026-01-31 14:32:15', + permissions: JSON.parse(JSON.stringify(DEFAULT_FIELD_PERMISSIONS)), + rateLimit: { requestsPerDay: 5000, requestsPerMonth: 150000 }, + billing: { plan: 'pro', usedCredits: 12580, totalCredits: 50000 }, + callStats: { today: 342, thisMonth: 8956, total: 45678 } + }, + { + id: 'key_2', + name: '点了码-测试环境', + key: 'sk-archer-dlm-test-g7h8i9j0k1l2', + secret: 'sec-dlm-m3n4o5p6q7r8', + status: 'active', + createdAt: '2026-01-20', + expiresAt: '2026-04-20', + lastUsed: '2026-01-30 09:15:42', + permissions: JSON.parse(JSON.stringify(DEFAULT_FIELD_PERMISSIONS)).map((g: FieldPermission) => ({ + ...g, + fields: g.fields.map(f => ({ ...f, enabled: f.price <= 2 })) + })), + rateLimit: { requestsPerDay: 1000, requestsPerMonth: 30000 }, + billing: { plan: 'basic', usedCredits: 2340, totalCredits: 10000 }, + callStats: { today: 56, thisMonth: 1234, total: 5678 } + }, +] + +// 生成随机密钥 +function generateKey(prefix: string): string { + const chars = 'abcdefghijklmnopqrstuvwxyz0123456789' + let result = prefix + for (let i = 0; i < 20; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)) + } + return result +} + +// GET: 获取API密钥列表 +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const action = searchParams.get('action') + const keyId = searchParams.get('id') + + // 验证单个密钥 + if (action === 'validate') { + const apiKey = searchParams.get('key') + const apiSecret = searchParams.get('secret') + + const key = apiKeys.find(k => k.key === apiKey && k.secret === apiSecret) + + if (!key) { + return NextResponse.json({ + success: false, + error: '无效的API密钥' + }, { status: 401 }) + } + + if (key.status !== 'active') { + return NextResponse.json({ + success: false, + error: `密钥状态: ${key.status}` + }, { status: 403 }) + } + + // 检查是否过期 + if (key.expiresAt && new Date(key.expiresAt) < new Date()) { + return NextResponse.json({ + success: false, + error: '密钥已过期' + }, { status: 403 }) + } + + return NextResponse.json({ + success: true, + data: { + id: key.id, + name: key.name, + plan: key.billing.plan, + permissions: key.permissions, + rateLimit: key.rateLimit, + creditsRemaining: key.billing.totalCredits === -1 ? -1 : key.billing.totalCredits - key.billing.usedCredits + } + }) + } + + // 获取单个密钥详情 + if (keyId) { + const key = apiKeys.find(k => k.id === keyId) + if (!key) { + return NextResponse.json({ + success: false, + error: '密钥不存在' + }, { status: 404 }) + } + return NextResponse.json({ + success: true, + data: key + }) + } + + // 获取所有密钥列表 + return NextResponse.json({ + success: true, + data: apiKeys.map(k => ({ + ...k, + key: k.key.substring(0, 12) + '••••••••••••', // 脱敏显示 + secret: '••••••••••••••••' + })), + total: apiKeys.length + }) + + } catch (error) { + console.error('获取API密钥失败:', error) + return NextResponse.json({ + success: false, + error: '获取API密钥失败' + }, { status: 500 }) + } +} + +// POST: 创建新API密钥 +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const { name, plan = 'basic', expiresAt } = body + + if (!name) { + return NextResponse.json({ + success: false, + error: '请提供密钥名称' + }, { status: 400 }) + } + + const planConfig = BILLING_PLANS[plan as keyof typeof BILLING_PLANS] || BILLING_PLANS.basic + + const newKey: APIKey = { + id: `key_${Date.now()}`, + name, + key: generateKey('sk-archer-'), + secret: generateKey('sec-'), + status: 'active', + createdAt: new Date().toISOString().split('T')[0], + expiresAt: expiresAt || null, + lastUsed: null, + permissions: JSON.parse(JSON.stringify(DEFAULT_FIELD_PERMISSIONS)), + rateLimit: { + requestsPerDay: planConfig.requestsPerDay, + requestsPerMonth: planConfig.requestsPerMonth + }, + billing: { + plan: plan as 'free' | 'basic' | 'pro' | 'enterprise', + usedCredits: 0, + totalCredits: planConfig.credits + }, + callStats: { today: 0, thisMonth: 0, total: 0 } + } + + apiKeys.push(newKey) + + return NextResponse.json({ + success: true, + data: newKey, + message: '密钥创建成功' + }) + + } catch (error) { + console.error('创建API密钥失败:', error) + return NextResponse.json({ + success: false, + error: '创建API密钥失败' + }, { status: 500 }) + } +} + +// PUT: 更新API密钥 +export async function PUT(request: NextRequest) { + try { + const body = await request.json() + const { id, action, permissions, status } = body + + const keyIndex = apiKeys.findIndex(k => k.id === id) + if (keyIndex === -1) { + return NextResponse.json({ + success: false, + error: '密钥不存在' + }, { status: 404 }) + } + + // 更新权限 + if (action === 'updatePermissions' && permissions) { + apiKeys[keyIndex].permissions = permissions + return NextResponse.json({ + success: true, + data: apiKeys[keyIndex], + message: '权限更新成功' + }) + } + + // 切换状态 + if (action === 'toggleStatus') { + apiKeys[keyIndex].status = apiKeys[keyIndex].status === 'active' ? 'disabled' : 'active' + return NextResponse.json({ + success: true, + data: apiKeys[keyIndex], + message: `密钥已${apiKeys[keyIndex].status === 'active' ? '启用' : '禁用'}` + }) + } + + // 重新生成密钥 + if (action === 'regenerate') { + apiKeys[keyIndex].key = generateKey('sk-archer-') + apiKeys[keyIndex].secret = generateKey('sec-') + return NextResponse.json({ + success: true, + data: apiKeys[keyIndex], + message: '密钥已重新生成' + }) + } + + // 常规更新 + if (status) { + apiKeys[keyIndex].status = status + } + + return NextResponse.json({ + success: true, + data: apiKeys[keyIndex], + message: '更新成功' + }) + + } catch (error) { + console.error('更新API密钥失败:', error) + return NextResponse.json({ + success: false, + error: '更新API密钥失败' + }, { status: 500 }) + } +} + +// DELETE: 删除API密钥 +export async function DELETE(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const id = searchParams.get('id') + + if (!id) { + return NextResponse.json({ + success: false, + error: '请提供密钥ID' + }, { status: 400 }) + } + + const keyIndex = apiKeys.findIndex(k => k.id === id) + if (keyIndex === -1) { + return NextResponse.json({ + success: false, + error: '密钥不存在' + }, { status: 404 }) + } + + const deletedKey = apiKeys.splice(keyIndex, 1)[0] + + return NextResponse.json({ + success: true, + data: { id: deletedKey.id, name: deletedKey.name }, + message: '密钥已删除' + }) + + } catch (error) { + console.error('删除API密钥失败:', error) + return NextResponse.json({ + success: false, + error: '删除API密钥失败' + }, { status: 500 }) + } +} diff --git a/app/api/docs/route.ts b/app/api/docs/route.ts new file mode 100644 index 0000000..a033e43 --- /dev/null +++ b/app/api/docs/route.ts @@ -0,0 +1,789 @@ +import { NextRequest, NextResponse } from 'next/server' + +// 神射手开放API文档 - OpenAPI 3.0 格式 +const OPENAPI_SPEC = { + openapi: '3.0.3', + info: { + title: '神射手数据中台 API', + description: ` +神射手是一个用户资产数字化平台,提供用户画像、AI标签、RFM评分等数据服务。 + +## 认证方式 +所有API请求需要在Header中携带API密钥: +- **Authorization**: Bearer YOUR_API_KEY(必需) +- **X-API-Secret**: YOUR_API_SECRET(可选,增强安全性) + +## 基础信息 +- 基础URL: https://your-domain.com/api/shensheshou +- 请求格式: JSON +- 响应格式: JSON +- 字符编码: UTF-8 + +## 计费说明 +- 每次API调用消耗积分(credits) +- 不同接口消耗积分不同 +- 批量接口享受折扣 + +## 字段权限 +返回字段根据API密钥配置的字段权限决定,可在开放API管理中配置。 + `.trim(), + version: '1.0.0', + contact: { + name: '神射手技术支持', + email: 'support@shensheshou.com', + }, + }, + servers: [ + { + url: '{protocol}://{host}/api/shensheshou', + description: '神射手API服务器', + variables: { + protocol: { default: 'https', enum: ['https', 'http'] }, + host: { default: 'your-domain.com' }, + }, + }, + ], + tags: [ + { name: 'user', description: '用户查询服务 - 查询用户画像和基本信息' }, + { name: 'tag', description: '标签服务 - 获取、应用和管理用户标签' }, + { name: 'ai', description: 'AI服务 - AI对话、智能分析和自动打标' }, + { name: 'data', description: '数据服务 - 数据流入和同步' }, + { name: 'package', description: '流量包 - 创建和导出用户流量包' }, + ], + paths: { + '/': { + get: { + tags: ['user'], + operationId: 'queryUser', + summary: '用户画像查询', + description: '根据手机号或QQ查询完整用户画像。返回字段根据API密钥权限决定。', + parameters: [ + { name: 'endpoint', in: 'query', required: true, schema: { type: 'string', enum: ['user'] }, description: '固定值: user' }, + { name: 'phone', in: 'query', required: false, schema: { type: 'string', pattern: '^1[3-9]\\d{9}$' }, description: '11位手机号' }, + { name: 'qq', in: 'query', required: false, schema: { type: 'string', pattern: '^[1-9]\\d{4,10}$' }, description: 'QQ号码(5-11位)' }, + { name: 'fields', in: 'query', required: false, schema: { type: 'string' }, description: '指定返回字段(逗号分隔): phone,qq,rfm_score,user_level,tags,behavior,location' }, + ], + responses: { + '200': { + description: '查询成功', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/UserResponse' }, + example: { + success: true, + data: { + phone: '138****8000', + rfm_score: 85, + user_level: 'A', + tags: ['高价值', '活跃用户', '电商偏好'], + last_active: '2026-01-30', + }, + credits_used: 5, + credits_remaining: 995, + }, + }, + }, + }, + '401': { $ref: '#/components/responses/Unauthorized' }, + '404': { $ref: '#/components/responses/NotFound' }, + }, + security: [{ bearerAuth: [] }], + }, + post: { + tags: ['user', 'data', 'ai'], + operationId: 'postEndpoint', + summary: '通用POST端点', + description: '根据endpoint参数调用不同服务:users/batch(批量查询)、ingest(数据流入)、ai/chat(AI对话)、ai/tag(AI打标)等', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/PostRequest' }, + examples: { + batch_query: { + summary: '批量用户查询', + value: { + endpoint: 'users/batch', + phones: ['13800138001', '13800138002'], + fields: ['rfm_score', 'tags', 'user_level'], + }, + }, + data_ingest: { + summary: '数据流入', + value: { + endpoint: 'ingest', + source: 'cunkebao', + users: [ + { phone: '13800138001', name: '张三', tags: ['高意向'] }, + { phone: '13800138002', name: '李四' }, + ], + }, + }, + ai_chat: { + summary: 'AI对话', + value: { + endpoint: 'ai/chat', + message: '帮我查询13800138000的用户画像', + }, + }, + ai_tag: { + summary: 'AI智能打标', + value: { + endpoint: 'ai/tag', + phones: ['13800138001', '13800138002'], + strategy: 'rfm', + }, + }, + }, + }, + }, + }, + responses: { + '200': { + description: '请求成功', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/GenericResponse' }, + }, + }, + }, + '401': { $ref: '#/components/responses/Unauthorized' }, + '400': { $ref: '#/components/responses/BadRequest' }, + }, + security: [{ bearerAuth: [] }], + }, + }, + '/tags': { + get: { + tags: ['tag'], + operationId: 'listTags', + summary: '获取标签列表', + description: '获取系统中所有可用标签及其统计信息', + parameters: [ + { name: 'category', in: 'query', required: false, schema: { type: 'string', enum: ['value', 'behavior', 'preference', 'custom'] }, description: '标签分类' }, + ], + responses: { + '200': { + description: '成功', + content: { + 'application/json': { + example: { + success: true, + data: { + tags: [ + { id: 'tag_001', name: '高价值用户', category: 'value', count: 125000 }, + { id: 'tag_002', name: '活跃用户', category: 'behavior', count: 450000 }, + ], + total: 45, + }, + }, + }, + }, + }, + }, + security: [{ bearerAuth: [] }], + }, + }, + '/packages': { + get: { + tags: ['package'], + operationId: 'listPackages', + summary: '获取流量包列表', + description: '获取已创建的流量包列表', + responses: { + '200': { + description: '成功', + content: { + 'application/json': { + example: { + success: true, + data: { + packages: [ + { id: 'pkg_001', name: '高价值用户包', count: 5000, created_at: '2026-01-30' }, + ], + total: 10, + }, + }, + }, + }, + }, + }, + security: [{ bearerAuth: [] }], + }, + post: { + tags: ['package'], + operationId: 'createPackage', + summary: '创建流量包', + description: '根据筛选条件创建用户流量包', + requestBody: { + required: true, + content: { + 'application/json': { + example: { + endpoint: 'packages/create', + name: '高价值用户包', + filters: { + user_level: ['S', 'A'], + tags: ['高价值'], + }, + }, + }, + }, + }, + responses: { + '200': { + description: '创建成功', + content: { + 'application/json': { + example: { + success: true, + data: { + package_id: 'pkg_002', + name: '高价值用户包', + count: 3500, + }, + }, + }, + }, + }, + }, + security: [{ bearerAuth: [] }], + }, + }, + }, + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + description: 'API密钥,格式: sk-xxx', + }, + apiSecret: { + type: 'apiKey', + in: 'header', + name: 'X-API-Secret', + description: 'API密钥Secret(可选,增强安全性)', + }, + }, + schemas: { + UserResponse: { + type: 'object', + properties: { + success: { type: 'boolean' }, + data: { + type: 'object', + properties: { + phone: { type: 'string', description: '脱敏手机号' }, + qq: { type: 'string', description: 'QQ号' }, + rfm_score: { type: 'integer', minimum: 0, maximum: 100, description: 'RFM综合评分' }, + user_level: { type: 'string', enum: ['S', 'A', 'B', 'C', 'D'], description: '用户等级' }, + tags: { type: 'array', items: { type: 'string' }, description: '用户标签列表' }, + behavior: { type: 'object', description: '行为数据' }, + location: { type: 'object', description: '位置信息' }, + last_active: { type: 'string', format: 'date', description: '最后活跃时间' }, + }, + }, + credits_used: { type: 'integer', description: '本次消耗积分' }, + credits_remaining: { type: 'integer', description: '剩余积分' }, + }, + }, + PostRequest: { + type: 'object', + required: ['endpoint'], + properties: { + endpoint: { type: 'string', description: '端点名称' }, + }, + additionalProperties: true, + }, + GenericResponse: { + type: 'object', + properties: { + success: { type: 'boolean' }, + data: { type: 'object' }, + message: { type: 'string' }, + credits_used: { type: 'integer' }, + credits_remaining: { type: 'integer' }, + }, + }, + Error: { + type: 'object', + properties: { + success: { type: 'boolean', example: false }, + error: { type: 'string' }, + code: { type: 'string' }, + }, + }, + }, + responses: { + Unauthorized: { + description: '认证失败', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/Error' }, + example: { success: false, error: '无效的API密钥', code: 'INVALID_API_KEY' }, + }, + }, + }, + NotFound: { + description: '资源不存在', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/Error' }, + example: { success: false, error: '用户不存在', code: 'USER_NOT_FOUND' }, + }, + }, + }, + BadRequest: { + description: '请求参数错误', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/Error' }, + example: { success: false, error: '缺少必要参数', code: 'MISSING_PARAMS' }, + }, + }, + }, + }, + }, +} + +// Markdown格式的API文档 +const MARKDOWN_DOC = `# 神射手数据中台 API 文档 + +> 版本: 1.0.0 | 更新时间: 2026-01-31 + +## 概述 + +神射手是一个**用户资产数字化平台**,提供用户画像、AI标签、RFM评分等数据服务。通过本API,第三方应用可以: + +- 查询用户画像和标签 +- 批量导入用户数据并自动完善标签 +- 使用AI进行智能对话和分析 +- 创建和导出用户流量包 + +--- + +## 认证方式 + +所有API请求需要在Header中携带API密钥: + +\`\`\` +Authorization: Bearer YOUR_API_KEY +X-API-Secret: YOUR_API_SECRET # 可选,增强安全性 +Content-Type: application/json +\`\`\` + +### 获取API密钥 + +1. 登录神射手管理后台 +2. 进入「数据市场 → 开放接口」 +3. 添加接入方并获取API Key和Secret + +--- + +## 基础URL + +\`\`\` +https://your-domain.com/api/shensheshou +\`\`\` + +--- + +## 接口列表 + +### 1. 用户画像查询 + +查询单个用户的完整画像信息。 + +**请求** + +\`\`\`http +GET /api/shensheshou?endpoint=user&phone=13800138000 +\`\`\` + +**参数** + +| 参数 | 类型 | 必需 | 说明 | +|------|------|:----:|------| +| endpoint | string | 是 | 固定值: user | +| phone | string | 否 | 11位手机号(与qq二选一) | +| qq | string | 否 | QQ号码(与phone二选一) | +| fields | string | 否 | 指定返回字段,逗号分隔 | + +**可用字段**: phone, qq, wechat, rfm_score, user_level, tags, behavior, location + +**响应示例** + +\`\`\`json +{ + "success": true, + "data": { + "phone": "138****8000", + "rfm_score": 85, + "user_level": "A", + "tags": ["高价值", "活跃用户", "电商偏好"], + "last_active": "2026-01-30" + }, + "credits_used": 5, + "credits_remaining": 995 +} +\`\`\` + +--- + +### 2. 批量用户查询 + +批量查询多个用户画像,最多100个/次。 + +**请求** + +\`\`\`http +POST /api/shensheshou +Content-Type: application/json + +{ + "endpoint": "users/batch", + "phones": ["13800138001", "13800138002"], + "fields": ["rfm_score", "tags", "user_level"] +} +\`\`\` + +**响应示例** + +\`\`\`json +{ + "success": true, + "data": { + "users": [ + {"phone": "138****8001", "rfm_score": 78, "tags": ["活跃"], "user_level": "B"}, + {"phone": "138****8002", "rfm_score": 92, "tags": ["高价值"], "user_level": "A"} + ], + "found": 2, + "not_found": [] + }, + "credits_used": 8 +} +\`\`\` + +--- + +### 3. 数据流入 + +将外部系统用户数据导入神射手,自动完善标签。 + +**请求** + +\`\`\`http +POST /api/shensheshou +Content-Type: application/json + +{ + "endpoint": "ingest", + "source": "cunkebao", + "users": [ + {"phone": "13800138001", "name": "张三", "tags": ["高意向"]}, + {"phone": "13800138002", "name": "李四"} + ] +} +\`\`\` + +**响应示例** + +\`\`\`json +{ + "success": true, + "data": { + "total": 2, + "processed": 2, + "enriched": 2, + "new_tags_added": 8 + } +} +\`\`\` + +--- + +### 4. AI对话 + +与神射手AI进行自然语言对话,支持用户查询、数据分析等。 + +**请求** + +\`\`\`http +POST /api/shensheshou +Content-Type: application/json + +{ + "endpoint": "ai/chat", + "message": "帮我查询13800138000的用户画像" +} +\`\`\` + +**响应示例** + +\`\`\`json +{ + "success": true, + "data": { + "reply": "该用户是A级高价值用户,RFM评分85分,标签包括:高价值、活跃用户、电商偏好。最近30天内有5次互动记录。", + "user_data": {...} + } +} +\`\`\` + +--- + +### 5. AI智能打标 + +使用AI为用户批量打标签。 + +**请求** + +\`\`\`http +POST /api/shensheshou +Content-Type: application/json + +{ + "endpoint": "ai/tag", + "phones": ["13800138001", "13800138002"], + "strategy": "rfm" +} +\`\`\` + +**策略选项**: rfm(RFM评分), behavior(行为分析), preference(偏好分析) + +--- + +### 6. 获取标签列表 + +获取系统中所有可用标签。 + +**请求** + +\`\`\`http +GET /api/shensheshou?endpoint=tags&category=value +\`\`\` + +--- + +### 7. 创建流量包 + +根据条件筛选用户并创建流量包。 + +**请求** + +\`\`\`http +POST /api/shensheshou +Content-Type: application/json + +{ + "endpoint": "packages/create", + "name": "高价值用户包", + "filters": { + "user_level": ["S", "A"], + "tags": ["高价值"] + } +} +\`\`\` + +--- + +## 错误码 + +| 错误码 | 说明 | +|--------|------| +| INVALID_API_KEY | API密钥无效或已过期 | +| PERMISSION_DENIED | 无权限访问该接口 | +| FIELD_NOT_ALLOWED | 无权限访问该字段 | +| QUOTA_EXCEEDED | 调用配额已用尽 | +| RATE_LIMITED | 请求频率超限 | +| USER_NOT_FOUND | 用户不存在 | +| MISSING_PARAMS | 缺少必要参数 | +| INVALID_PARAMS | 参数格式错误 | + +--- + +## 计费说明 + +| 接口 | 单价(积分/次) | +|------|----------------| +| 用户画像查询 | 1 | +| 批量用户查询 | 0.8/人 | +| 数据流入 | 0.5/人 | +| AI对话 | 5 | +| AI智能打标 | 2/人 | +| 流量包创建 | 10 | +| 流量包导出 | 0.1/人 | + +--- + +## SDK示例 + +### Python + +\`\`\`python +import requests + +API_KEY = "sk-archer-xxxxx" +BASE_URL = "https://your-domain.com/api/shensheshou" + +headers = { + "Authorization": f"Bearer {API_KEY}", + "Content-Type": "application/json" +} + +# 查询用户画像 +response = requests.get( + f"{BASE_URL}?endpoint=user&phone=13800138000", + headers=headers +) +print(response.json()) + +# 批量查询 +response = requests.post(BASE_URL, headers=headers, json={ + "endpoint": "users/batch", + "phones": ["13800138001", "13800138002"] +}) +print(response.json()) +\`\`\` + +### JavaScript + +\`\`\`javascript +const API_KEY = "sk-archer-xxxxx"; +const BASE_URL = "https://your-domain.com/api/shensheshou"; + +// 查询用户画像 +const response = await fetch(\`\${BASE_URL}?endpoint=user&phone=13800138000\`, { + headers: { + "Authorization": \`Bearer \${API_KEY}\`, + } +}); +const data = await response.json(); +console.log(data); + +// 批量查询 +const batchResponse = await fetch(BASE_URL, { + method: "POST", + headers: { + "Authorization": \`Bearer \${API_KEY}\`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + endpoint: "users/batch", + phones: ["13800138001", "13800138002"] + }) +}); +\`\`\` + +### cURL + +\`\`\`bash +# 查询用户画像 +curl -X GET "https://your-domain.com/api/shensheshou?endpoint=user&phone=13800138000" \\ + -H "Authorization: Bearer sk-archer-xxxxx" + +# 批量查询 +curl -X POST "https://your-domain.com/api/shensheshou" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "Content-Type: application/json" \\ + -d '{"endpoint":"users/batch","phones":["13800138001","13800138002"]}' +\`\`\` + +--- + +## 联系支持 + +- 技术支持邮箱: support@shensheshou.com +- 在线文档: https://docs.shensheshou.com + +--- + +*文档版本 1.0.0 - 最后更新: 2026-01-31* +` + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url) + const format = searchParams.get('format') || 'json' + + // 获取当前域名作为服务器URL + const host = request.headers.get('host') || 'localhost:3000' + const protocol = request.headers.get('x-forwarded-proto') || 'http' + + // 更新服务器URL + const spec = { + ...OPENAPI_SPEC, + servers: [ + { + url: `${protocol}://${host}/api/shensheshou`, + description: '神射手API服务器', + }, + ], + } + + if (format === 'openapi' || format === 'json') { + return NextResponse.json(spec, { + headers: { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + 'Cache-Control': 'public, max-age=3600', + }, + }) + } + + if (format === 'markdown' || format === 'md') { + return new NextResponse(MARKDOWN_DOC, { + headers: { + 'Content-Type': 'text/markdown; charset=utf-8', + 'Content-Disposition': 'attachment; filename="shensheshou-api-docs.md"', + 'Access-Control-Allow-Origin': '*', + }, + }) + } + + if (format === 'download') { + return NextResponse.json(spec, { + headers: { + 'Content-Type': 'application/json', + 'Content-Disposition': 'attachment; filename="shensheshou-openapi.json"', + 'Access-Control-Allow-Origin': '*', + }, + }) + } + + // 返回简化版文档信息 + return NextResponse.json({ + name: '神射手数据中台 API', + version: '1.0.0', + description: '用户资产数字化平台API,提供用户画像、AI标签、RFM评分等数据服务', + docs: { + openapi: `${protocol}://${host}/api/docs?format=openapi`, + markdown: `${protocol}://${host}/api/docs?format=markdown`, + download: `${protocol}://${host}/api/docs?format=download`, + web: `${protocol}://${host}/data-market/api/docs`, + }, + endpoints: { + base: `${protocol}://${host}/api/shensheshou`, + auth: 'Header: Authorization: Bearer YOUR_API_KEY', + }, + quick_start: { + step1: '获取API密钥:登录管理后台 → 数据市场 → 开放接口', + step2: '阅读文档:访问上方 docs.web 链接', + step3: '测试调用:使用curl或SDK调用API', + }, + }, { + headers: { + 'Access-Control-Allow-Origin': '*', + }, + }) +} + +// 支持CORS +export async function OPTIONS() { + return new NextResponse(null, { + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + }) +} diff --git a/app/api/shensheshou/route.ts b/app/api/shensheshou/route.ts new file mode 100644 index 0000000..7f7aaa4 --- /dev/null +++ b/app/api/shensheshou/route.ts @@ -0,0 +1,640 @@ +import { NextRequest, NextResponse } from 'next/server' + +// ==================== 类型定义 ==================== + +interface FieldPermission { + fieldGroup: string + fields: { + name: string + label: string + enabled: boolean + price: number + }[] +} + +interface APIKeyInfo { + id: string + name: string + plan: string + permissions: FieldPermission[] + rateLimit: { + requestsPerDay: number + requestsPerMonth: number + } + creditsRemaining: number +} + +interface UserProfile { + phone?: string + qq?: string + wechat?: string + email?: string + nickname?: string + rfm_score?: number + user_level?: string + value_score?: number + activity_score?: number + loyalty_score?: number + basic_tags?: string[] + behavior_tags?: string[] + preference_tags?: string[] + ai_tags?: string[] + custom_tags?: string[] + last_active?: string + visit_count?: number + purchase_history?: object[] + interaction_log?: object[] + channel_source?: string + social_bindings?: object + device_info?: object + location_data?: object + risk_assessment?: object + ai_insights?: object +} + +// ==================== 模拟数据 ==================== + +// 模拟用户数据库 +const MOCK_USERS: Record = { + '13800138000': { + phone: '13800138000', + qq: '123456789', + wechat: 'wx_zhangsan', + email: 'zhangsan@example.com', + nickname: '张三', + rfm_score: 85, + user_level: 'A', + value_score: 92, + activity_score: 78, + loyalty_score: 88, + basic_tags: ['高价值', '活跃用户'], + behavior_tags: ['频繁购买', '喜欢促销'], + preference_tags: ['数码产品', '时尚服饰'], + ai_tags: ['潜在VIP', '消费升级'], + custom_tags: ['重点客户'], + last_active: '2026-01-30 15:30:00', + visit_count: 156, + purchase_history: [{ date: '2026-01-28', amount: 2580 }], + interaction_log: [{ type: 'click', page: 'product', time: '2026-01-30' }], + channel_source: '微信公众号', + social_bindings: { wechat: true, qq: true, weibo: false }, + device_info: { os: 'iOS', browser: 'Safari' }, + location_data: { city: '上海', district: '浦东新区' }, + risk_assessment: { level: 'low', score: 15 }, + ai_insights: { nextAction: '推荐新品', churnRisk: 0.08 } + }, + '13900139000': { + phone: '13900139000', + qq: '987654321', + nickname: '李四', + rfm_score: 65, + user_level: 'B', + value_score: 58, + activity_score: 72, + loyalty_score: 60, + basic_tags: ['普通用户'], + behavior_tags: ['偶尔购买'], + last_active: '2026-01-25 10:15:00', + visit_count: 45, + channel_source: '搜索引擎' + }, + '15800158000': { + phone: '15800158000', + nickname: '王五', + rfm_score: 45, + user_level: 'C', + value_score: 38, + activity_score: 52, + loyalty_score: 40, + basic_tags: ['新用户'], + last_active: '2026-01-20 08:30:00', + visit_count: 12, + channel_source: '广告投放' + } +} + +// 模拟标签数据 +const MOCK_TAGS = [ + { id: 'tag_1', name: '高价值', category: '价值标签', count: 12500 }, + { id: 'tag_2', name: '活跃用户', category: '行为标签', count: 35800 }, + { id: 'tag_3', name: '潜在VIP', category: 'AI标签', count: 8900 }, + { id: 'tag_4', name: '流失预警', category: 'AI标签', count: 4500 }, + { id: 'tag_5', name: '新用户', category: '基础标签', count: 28000 }, + { id: 'tag_6', name: '频繁购买', category: '行为标签', count: 15600 }, +] + +// ==================== 辅助函数 ==================== + +// 验证API密钥 +async function validateAPIKey(request: NextRequest): Promise<{ valid: boolean; keyInfo?: APIKeyInfo; error?: string }> { + const authHeader = request.headers.get('Authorization') + const apiSecret = request.headers.get('X-API-Secret') + + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return { valid: false, error: '缺少Authorization头' } + } + + const apiKey = authHeader.replace('Bearer ', '') + + // 调用密钥验证API + try { + const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000' + const response = await fetch(`${baseUrl}/api/api-keys?action=validate&key=${apiKey}&secret=${apiSecret || ''}`) + const result = await response.json() + + if (!result.success) { + return { valid: false, error: result.error } + } + + return { valid: true, keyInfo: result.data } + } catch { + // 备用验证(开发环境) + if (apiKey.startsWith('sk-archer-')) { + return { + valid: true, + keyInfo: { + id: 'dev_key', + name: '开发测试密钥', + plan: 'pro', + permissions: [], + rateLimit: { requestsPerDay: -1, requestsPerMonth: -1 }, + creditsRemaining: -1 + } + } + } + return { valid: false, error: '密钥验证失败' } + } +} + +// 过滤用户数据(根据权限) +function filterUserData(user: UserProfile, permissions: FieldPermission[], requestedFields?: string[]): Partial { + const allowedFields = new Set() + let totalPrice = 0 + + // 收集所有允许的字段 + permissions.forEach(group => { + group.fields.forEach(field => { + if (field.enabled) { + allowedFields.add(field.name) + if (!requestedFields || requestedFields.includes(field.name)) { + totalPrice += field.price + } + } + }) + }) + + // 如果指定了字段,只返回指定的字段 + const fieldsToReturn = requestedFields + ? requestedFields.filter(f => allowedFields.has(f)) + : Array.from(allowedFields) + + const filteredUser: Partial = {} + + fieldsToReturn.forEach(field => { + if (field in user) { + (filteredUser as Record)[field] = (user as Record)[field] + } + }) + + return filteredUser +} + +// 计算信用点消耗 +function calculateCredits(permissions: FieldPermission[], requestedFields?: string[]): number { + let total = 0 + + permissions.forEach(group => { + group.fields.forEach(field => { + if (field.enabled && (!requestedFields || requestedFields.includes(field.name))) { + total += field.price + } + }) + }) + + return total +} + +// 记录API调用 +async function logAPICall(keyId: string, keyName: string, endpoint: string, method: string, status: number, credits: number, responseTime: number, ip: string) { + try { + const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000' + await fetch(`${baseUrl}/api/api-keys/billing`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ keyId, keyName, endpoint, method, status, credits, responseTime, ip }) + }) + } catch (error) { + console.error('记录API调用失败:', error) + } +} + +// ==================== API路由 ==================== + +// GET: 查询用户/标签等 +export async function GET(request: NextRequest) { + const startTime = Date.now() + const { searchParams } = new URL(request.url) + const endpoint = searchParams.get('endpoint') || 'user' + const ip = request.headers.get('x-forwarded-for') || 'unknown' + + // 验证API密钥 + const { valid, keyInfo, error } = await validateAPIKey(request) + if (!valid || !keyInfo) { + return NextResponse.json({ + success: false, + error: error || '认证失败' + }, { status: 401 }) + } + + try { + // 用户查询 + if (endpoint === 'user') { + const phone = searchParams.get('phone') + const qq = searchParams.get('qq') + const fields = searchParams.get('fields')?.split(',') + + if (!phone && !qq) { + return NextResponse.json({ + success: false, + error: '请提供phone或qq参数' + }, { status: 400 }) + } + + // 查找用户 + const user = phone + ? MOCK_USERS[phone] + : Object.values(MOCK_USERS).find(u => u.qq === qq) + + if (!user) { + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/user', 'GET', 404, 0, Date.now() - startTime, ip) + return NextResponse.json({ + success: false, + error: '用户不存在' + }, { status: 404 }) + } + + // 过滤数据 + const filteredUser = filterUserData(user, keyInfo.permissions, fields) + const creditsUsed = calculateCredits(keyInfo.permissions, fields) + + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/user', 'GET', 200, creditsUsed, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: filteredUser, + credits_used: creditsUsed, + credits_remaining: keyInfo.creditsRemaining === -1 ? '无限' : keyInfo.creditsRemaining - creditsUsed + }) + } + + // 标签列表 + if (endpoint === 'tags') { + const category = searchParams.get('category') + let tags = MOCK_TAGS + + if (category) { + tags = tags.filter(t => t.category === category) + } + + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/tags', 'GET', 200, 0.5, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: tags, + total: tags.length, + credits_used: 0.5 + }) + } + + // 数据源列表 + if (endpoint === 'sources') { + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/sources', 'GET', 200, 0.5, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: [ + { id: 'KR_腾讯', name: '腾讯数据', status: 'connected', records: 700000000 }, + { id: 'KR_微博', name: '微博数据', status: 'connected', records: 140000000 }, + { id: 'KR_京东', name: '京东数据', status: 'connected', records: 50000000 }, + { id: 'KR_存客宝', name: '存客宝数据', status: 'connected', records: 1200000 }, + ], + credits_used: 0.5 + }) + } + + // 流量包列表 + if (endpoint === 'packages') { + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/packages', 'GET', 200, 0.5, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: [ + { id: 'pkg_1', name: '高价值用户包', count: 12500, createdAt: '2026-01-28' }, + { id: 'pkg_2', name: '活跃用户包', count: 35800, createdAt: '2026-01-25' }, + { id: 'pkg_3', name: '流失预警包', count: 4500, createdAt: '2026-01-20' }, + ], + credits_used: 0.5 + }) + } + + return NextResponse.json({ + success: false, + error: '未知的endpoint' + }, { status: 400 }) + + } catch (error) { + console.error('API请求处理失败:', error) + return NextResponse.json({ + success: false, + error: '服务器内部错误' + }, { status: 500 }) + } +} + +// POST: 批量查询/AI服务/数据导入等 +export async function POST(request: NextRequest) { + const startTime = Date.now() + const ip = request.headers.get('x-forwarded-for') || 'unknown' + + // 验证API密钥 + const { valid, keyInfo, error } = await validateAPIKey(request) + if (!valid || !keyInfo) { + return NextResponse.json({ + success: false, + error: error || '认证失败' + }, { status: 401 }) + } + + try { + const body = await request.json() + const { endpoint } = body + + // 批量用户查询 + if (endpoint === 'users/batch') { + const { phones = [], qqs = [], fields } = body + + if (phones.length === 0 && qqs.length === 0) { + return NextResponse.json({ + success: false, + error: '请提供phones或qqs数组' + }, { status: 400 }) + } + + if (phones.length > 100 || qqs.length > 100) { + return NextResponse.json({ + success: false, + error: '单次查询最多100个用户' + }, { status: 400 }) + } + + const results: Partial[] = [] + + phones.forEach((phone: string) => { + const user = MOCK_USERS[phone] + if (user) { + results.push(filterUserData(user, keyInfo.permissions, fields)) + } + }) + + qqs.forEach((qq: string) => { + const user = Object.values(MOCK_USERS).find(u => u.qq === qq) + if (user) { + results.push(filterUserData(user, keyInfo.permissions, fields)) + } + }) + + const creditsUsed = results.length * 0.8 // 批量折扣 + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/users/batch', 'POST', 200, creditsUsed, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: results, + total: results.length, + credits_used: creditsUsed, + credits_remaining: keyInfo.creditsRemaining === -1 ? '无限' : keyInfo.creditsRemaining - creditsUsed + }) + } + + // AI对话 + if (endpoint === 'ai/chat') { + const { message } = body + + if (!message) { + return NextResponse.json({ + success: false, + error: '请提供message参数' + }, { status: 400 }) + } + + // 模拟AI响应 + const creditsUsed = 5 + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/ai/chat', 'POST', 200, creditsUsed, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + response: { + content: `根据您的查询"${message}",系统分析结果如下:目前数据库中共有高价值用户12,500位,其中本月新增1,234位。建议重点关注RFM评分高于80的用户群体。`, + data: { + totalUsers: 12500, + newUsersThisMonth: 1234, + avgRFM: 72 + }, + suggestions: [ + '可以进一步筛选活跃度高于80的用户', + '建议导出为流量包进行精准营销' + ] + }, + credits_used: creditsUsed + }) + } + + // AI分析 + if (endpoint === 'ai/analyze') { + const { type } = body + + const creditsUsed = 10 + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/ai/analyze', 'POST', 200, creditsUsed, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: { + type, + summary: `${type}分析完成`, + insights: [ + { metric: 'RFM平均分', value: 72, trend: '+5%' }, + { metric: '活跃用户占比', value: '45%', trend: '+2%' }, + { metric: '高价值用户', value: 12500, trend: '+8%' } + ], + recommendations: [ + '建议对RFM分数65-75区间的用户进行激活营销', + '高价值用户流失风险较低,可减少维护投入' + ] + }, + credits_used: creditsUsed + }) + } + + // AI打标 + if (endpoint === 'ai/tag') { + const { user_ids = [] } = body + + const creditsUsed = user_ids.length * 0.5 + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/ai/tag', 'POST', 200, creditsUsed, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: { + processed: user_ids.length, + tagsApplied: user_ids.length * 3, + summary: `已为${user_ids.length}个用户完成AI智能打标` + }, + credits_used: creditsUsed + }) + } + + // 应用标签 + if (endpoint === 'tags/apply') { + const { user_id, tags } = body + + if (!user_id || !tags || !Array.isArray(tags)) { + return NextResponse.json({ + success: false, + error: '请提供user_id和tags数组' + }, { status: 400 }) + } + + const creditsUsed = 2 + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/tags/apply', 'POST', 200, creditsUsed, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: { + user_id, + applied_tags: tags, + timestamp: new Date().toISOString() + }, + credits_used: creditsUsed + }) + } + + // 数据导入 + if (endpoint === 'ingest') { + const { source, data: ingestData, auto_tag } = body + + if (!source || !ingestData || !Array.isArray(ingestData)) { + return NextResponse.json({ + success: false, + error: '请提供source和data数组' + }, { status: 400 }) + } + + const creditsUsed = ingestData.length * 0.1 + (auto_tag ? ingestData.length * 0.3 : 0) + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/ingest', 'POST', 200, creditsUsed, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: { + source, + imported: ingestData.length, + auto_tagged: auto_tag ? ingestData.length : 0, + task_id: `task_${Date.now()}` + }, + credits_used: creditsUsed + }) + } + + // 创建流量包 + if (endpoint === 'packages/create') { + const { name, criteria } = body + + if (!name) { + return NextResponse.json({ + success: false, + error: '请提供流量包名称' + }, { status: 400 }) + } + + const creditsUsed = 5 + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/packages/create', 'POST', 200, creditsUsed, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: { + id: `pkg_${Date.now()}`, + name, + criteria, + count: Math.floor(Math.random() * 10000) + 1000, + status: 'created', + createdAt: new Date().toISOString() + }, + credits_used: creditsUsed + }) + } + + // 导出流量包 + if (endpoint === 'packages/export') { + const { package_id, target } = body + + if (!package_id || !target) { + return NextResponse.json({ + success: false, + error: '请提供package_id和target' + }, { status: 400 }) + } + + const creditsUsed = 10 + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/packages/export', 'POST', 200, creditsUsed, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: { + package_id, + target, + status: 'exporting', + task_id: `export_${Date.now()}`, + estimatedTime: '2分钟' + }, + credits_used: creditsUsed + }) + } + + // 生成报告 + if (endpoint === 'report/generate') { + const { template } = body + + if (!template) { + return NextResponse.json({ + success: false, + error: '请提供报告模板' + }, { status: 400 }) + } + + const creditsUsed = 15 + await logAPICall(keyInfo.id, keyInfo.name, '/api/shensheshou/report/generate', 'POST', 200, creditsUsed, Date.now() - startTime, ip) + + return NextResponse.json({ + success: true, + data: { + template, + report_id: `report_${Date.now()}`, + status: 'generating', + estimatedTime: '5分钟', + downloadUrl: null + }, + credits_used: creditsUsed + }) + } + + return NextResponse.json({ + success: false, + error: '未知的endpoint' + }, { status: 400 }) + + } catch (error) { + console.error('API请求处理失败:', error) + return NextResponse.json({ + success: false, + error: '服务器内部错误' + }, { status: 500 }) + } +} diff --git a/app/components/ui/popover.tsx b/app/components/ui/popover.tsx index 286503f..cb5c141 100644 --- a/app/components/ui/popover.tsx +++ b/app/components/ui/popover.tsx @@ -1,6 +1,31 @@ "use client" + +import * as React from "react" import * as PopoverPrimitive from "@radix-ui/react-popover" +import { cn } from "@/lib/utils" + const Popover = PopoverPrimitive.Root -const PopoverTrigger +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent } diff --git a/app/data-market/api/docs/page.tsx b/app/data-market/api/docs/page.tsx new file mode 100644 index 0000000..382366f --- /dev/null +++ b/app/data-market/api/docs/page.tsx @@ -0,0 +1,888 @@ +"use client" + +import { useState, useEffect } from "react" +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Input } from "@/components/ui/input" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { + Copy, + CheckCircle2, + Search, + Tags, + Brain, + Database, + FileText, + Package, + ArrowLeft, + Code, + Zap, + Key, + DollarSign, + ChevronDown, + ChevronRight, + Play, + Terminal, + BookOpen, + Server, + Download, + ExternalLink, + Link2, + Share2, +} from "lucide-react" +import Link from "next/link" + +// API端点接口 +interface APIEndpoint { + id: string + method: 'GET' | 'POST' | 'PUT' | 'DELETE' + path: string + name: string + description: string + category: string + auth: boolean + price: number + params?: { name: string; type: string; required: boolean; desc: string }[] + response?: string + example?: string +} + +// API分类 +const API_CATEGORIES = [ + { id: 'query', name: '用户查询', icon: Search, description: '查询用户画像和基本信息' }, + { id: 'tag', name: '标签服务', icon: Tags, description: '获取和应用用户标签' }, + { id: 'ai', name: 'AI服务', icon: Brain, description: 'AI对话、分析和智能打标' }, + { id: 'data', name: '数据服务', icon: Database, description: '数据源管理和数据导入' }, + { id: 'report', name: '报告服务', icon: FileText, description: '生成数据分析报告' }, + { id: 'package', name: '流量包', icon: Package, description: '创建和导出流量包' }, +] + +// 预定义API端点 +const API_ENDPOINTS: APIEndpoint[] = [ + // 用户查询 + { + id: 'api_1', + method: 'GET', + path: '/api/shensheshou/user', + name: '用户画像查询', + description: '根据手机号或QQ查询完整用户画像,返回字段根据API密钥权限决定', + category: 'query', + auth: true, + price: 1, + params: [ + { name: 'phone', type: 'string', required: false, desc: '11位手机号' }, + { name: 'qq', type: 'string', required: false, desc: 'QQ号码' }, + { name: 'fields', type: 'string', required: false, desc: '指定返回字段,逗号分隔' }, + ], + response: `{ + "success": true, + "data": { + "phone": "138****8000", + "rfm_score": 85, + "user_level": "A", + "tags": ["高价值", "活跃用户"], + "last_active": "2026-01-30" + }, + "credits_used": 5, + "credits_remaining": 995 +}`, + example: `curl -X GET "https://api.shensheshou.com/api/shensheshou?endpoint=user&phone=13800138000&fields=rfm_score,tags" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx"`, + }, + { + id: 'api_2', + method: 'POST', + path: '/api/shensheshou/users/batch', + name: '批量用户查询', + description: '批量查询多个用户的画像信息,最多支持100个用户/次,享受批量折扣', + category: 'query', + auth: true, + price: 0.8, + params: [ + { name: 'endpoint', type: 'string', required: true, desc: '固定值: users/batch' }, + { name: 'phones', type: 'array', required: false, desc: '手机号数组,最多100个' }, + { name: 'qqs', type: 'array', required: false, desc: 'QQ号数组,最多100个' }, + { name: 'fields', type: 'array', required: false, desc: '指定返回字段数组' }, + ], + response: `{ + "success": true, + "data": [ + { "phone": "138****8000", "rfm_score": 85, "user_level": "A" }, + { "phone": "139****9000", "rfm_score": 72, "user_level": "B" } + ], + "total": 2, + "credits_used": 8, + "credits_remaining": 992 +}`, + example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx" \\ + -H "Content-Type: application/json" \\ + -d '{ + "endpoint": "users/batch", + "phones": ["13800138000", "13900139000"], + "fields": ["rfm_score", "user_level", "tags"] + }'`, + }, + // 标签服务 + { + id: 'api_3', + method: 'GET', + path: '/api/shensheshou/tags', + name: '标签列表', + description: '获取系统中所有可用标签及其分类和使用统计', + category: 'tag', + auth: true, + price: 0.5, + params: [ + { name: 'category', type: 'string', required: false, desc: '标签分类筛选' }, + ], + response: `{ + "success": true, + "data": [ + { "id": "tag_1", "name": "高价值", "category": "价值标签", "count": 12500 }, + { "id": "tag_2", "name": "活跃用户", "category": "行为标签", "count": 35800 } + ], + "total": 45, + "credits_used": 0.5 +}`, + example: `curl -X GET "https://api.shensheshou.com/api/shensheshou?endpoint=tags&category=价值标签" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx"`, + }, + { + id: 'api_4', + method: 'POST', + path: '/api/shensheshou/tags/apply', + name: '应用标签', + description: '为指定用户批量应用标签', + category: 'tag', + auth: true, + price: 2, + params: [ + { name: 'endpoint', type: 'string', required: true, desc: '固定值: tags/apply' }, + { name: 'user_id', type: 'string', required: true, desc: '用户ID或手机号' }, + { name: 'tags', type: 'array', required: true, desc: '要应用的标签ID数组' }, + { name: 'overwrite', type: 'boolean', required: false, desc: '是否覆盖现有标签,默认false' }, + ], + response: `{ + "success": true, + "data": { + "user_id": "13800138000", + "applied_tags": ["tag_1", "tag_2"], + "timestamp": "2026-01-31T14:30:00Z" + }, + "credits_used": 2 +}`, + example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx" \\ + -H "Content-Type: application/json" \\ + -d '{ + "endpoint": "tags/apply", + "user_id": "13800138000", + "tags": ["tag_1", "tag_2"], + "overwrite": false + }'`, + }, + // AI服务 + { + id: 'api_5', + method: 'POST', + path: '/api/shensheshou/ai/chat', + name: 'AI智能对话', + description: '与神射手AI进行对话,支持自然语言查询和数据分析', + category: 'ai', + auth: true, + price: 5, + params: [ + { name: 'endpoint', type: 'string', required: true, desc: '固定值: ai/chat' }, + { name: 'message', type: 'string', required: true, desc: '对话内容' }, + { name: 'context', type: 'object', required: false, desc: '上下文信息' }, + { name: 'model', type: 'string', required: false, desc: 'AI模型: qwen/deepseek' }, + ], + response: `{ + "success": true, + "response": { + "content": "根据查询,共有 1,234 位高价值用户...", + "data": { "count": 1234, "avg_rfm": 82 }, + "suggestions": ["可以进一步筛选活跃度", "建议导出为流量包"] + }, + "credits_used": 5 +}`, + example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx" \\ + -H "Content-Type: application/json" \\ + -d '{ + "endpoint": "ai/chat", + "message": "帮我分析RFM评分大于80的用户群体", + "model": "qwen" + }'`, + }, + { + id: 'api_6', + method: 'POST', + path: '/api/shensheshou/ai/analyze', + name: 'AI数据分析', + description: 'AI自动分析用户群体特征并生成洞察报告', + category: 'ai', + auth: true, + price: 10, + params: [ + { name: 'endpoint', type: 'string', required: true, desc: '固定值: ai/analyze' }, + { name: 'type', type: 'string', required: true, desc: '分析类型: rfm/behavior/preference/churn' }, + { name: 'filters', type: 'object', required: false, desc: '用户筛选条件' }, + { name: 'depth', type: 'string', required: false, desc: '分析深度: quick/standard/deep' }, + ], + response: `{ + "success": true, + "data": { + "type": "rfm", + "summary": "RFM分析完成", + "insights": [ + { "metric": "RFM平均分", "value": 72, "trend": "+5%" }, + { "metric": "高价值用户", "value": 12500, "trend": "+8%" } + ], + "recommendations": [ + "建议对RFM分数65-75区间的用户进行激活营销" + ] + }, + "credits_used": 10 +}`, + example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx" \\ + -H "Content-Type: application/json" \\ + -d '{ + "endpoint": "ai/analyze", + "type": "rfm", + "filters": { "user_level": ["A", "B"] }, + "depth": "standard" + }'`, + }, + { + id: 'api_7', + method: 'POST', + path: '/api/shensheshou/ai/tag', + name: 'AI智能打标', + description: 'AI自动分析用户数据并智能打标签', + category: 'ai', + auth: true, + price: 8, + params: [ + { name: 'endpoint', type: 'string', required: true, desc: '固定值: ai/tag' }, + { name: 'user_ids', type: 'array', required: true, desc: '用户ID数组' }, + { name: 'tag_types', type: 'array', required: false, desc: '指定标签类型' }, + { name: 'model', type: 'string', required: false, desc: 'AI模型选择' }, + ], + response: `{ + "success": true, + "data": { + "processed": 50, + "tagsApplied": 150, + "summary": "已为50个用户完成AI智能打标" + }, + "credits_used": 25 +}`, + }, + // 数据服务 + { + id: 'api_8', + method: 'GET', + path: '/api/shensheshou/sources', + name: '数据源列表', + description: '获取所有已接入的数据源及其连接状态和数据量', + category: 'data', + auth: true, + price: 0.5, + response: `{ + "success": true, + "data": [ + { "id": "KR_腾讯", "name": "腾讯数据", "status": "connected", "records": 700000000 }, + { "id": "KR_微博", "name": "微博数据", "status": "connected", "records": 140000000 } + ], + "credits_used": 0.5 +}`, + example: `curl -X GET "https://api.shensheshou.com/api/shensheshou?endpoint=sources" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx"`, + }, + { + id: 'api_9', + method: 'POST', + path: '/api/shensheshou/ingest', + name: '数据导入', + description: '导入外部数据到神射手平台,自动触发AI标签引擎处理', + category: 'data', + auth: true, + price: 3, + params: [ + { name: 'endpoint', type: 'string', required: true, desc: '固定值: ingest' }, + { name: 'source', type: 'string', required: true, desc: '数据源标识,如: cunkebao' }, + { name: 'data', type: 'array', required: true, desc: '用户数据数组' }, + { name: 'auto_tag', type: 'boolean', required: false, desc: '是否自动打标,默认true' }, + ], + response: `{ + "success": true, + "data": { + "source": "cunkebao", + "imported": 100, + "auto_tagged": 100, + "task_id": "task_1706698200000" + }, + "credits_used": 40 +}`, + example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx" \\ + -H "Content-Type: application/json" \\ + -d '{ + "endpoint": "ingest", + "source": "cunkebao", + "data": [ + { "phone": "13800138000", "name": "张三", "source": "微信" }, + { "phone": "13900139000", "name": "李四", "source": "抖音" } + ], + "auto_tag": true + }'`, + }, + // 报告服务 + { + id: 'api_10', + method: 'POST', + path: '/api/shensheshou/report/generate', + name: '生成分析报告', + description: 'AI自动生成数据分析报告,支持多种模板和格式', + category: 'report', + auth: true, + price: 15, + params: [ + { name: 'endpoint', type: 'string', required: true, desc: '固定值: report/generate' }, + { name: 'template', type: 'string', required: true, desc: '报告模板: user_insight/rfm_analysis/trend_report' }, + { name: 'date_range', type: 'object', required: false, desc: '日期范围 {start, end}' }, + { name: 'format', type: 'string', required: false, desc: '输出格式: pdf/html/json' }, + ], + response: `{ + "success": true, + "data": { + "template": "rfm_analysis", + "report_id": "report_1706698200000", + "status": "generating", + "estimatedTime": "5分钟", + "downloadUrl": null + }, + "credits_used": 15 +}`, + }, + // 流量包 + { + id: 'api_11', + method: 'GET', + path: '/api/shensheshou/packages', + name: '流量包列表', + description: '获取所有已创建的流量包', + category: 'package', + auth: true, + price: 0.5, + response: `{ + "success": true, + "data": [ + { "id": "pkg_1", "name": "高价值用户包", "count": 12500, "createdAt": "2026-01-28" }, + { "id": "pkg_2", "name": "活跃用户包", "count": 35800, "createdAt": "2026-01-25" } + ], + "credits_used": 0.5 +}`, + example: `curl -X GET "https://api.shensheshou.com/api/shensheshou?endpoint=packages" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx"`, + }, + { + id: 'api_12', + method: 'POST', + path: '/api/shensheshou/packages/create', + name: '创建流量包', + description: '根据筛选条件创建用户流量包', + category: 'package', + auth: true, + price: 5, + params: [ + { name: 'endpoint', type: 'string', required: true, desc: '固定值: packages/create' }, + { name: 'name', type: 'string', required: true, desc: '流量包名称' }, + { name: 'criteria', type: 'object', required: true, desc: '筛选条件' }, + { name: 'export_fields', type: 'array', required: false, desc: '导出字段' }, + ], + response: `{ + "success": true, + "data": { + "id": "pkg_1706698200000", + "name": "高价值活跃用户", + "criteria": { "rfm_score": { "$gte": 80 } }, + "count": 8500, + "status": "created", + "createdAt": "2026-01-31T14:30:00Z" + }, + "credits_used": 5 +}`, + example: `curl -X POST "https://api.shensheshou.com/api/shensheshou" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx" \\ + -H "Content-Type: application/json" \\ + -d '{ + "endpoint": "packages/create", + "name": "高价值活跃用户", + "criteria": { + "rfm_score": { "$gte": 80 }, + "user_level": ["A", "B"] + }, + "export_fields": ["phone", "rfm_score", "tags"] + }'`, + }, + { + id: 'api_13', + method: 'POST', + path: '/api/shensheshou/packages/export', + name: '导出流量包', + description: '导出流量包数据到指定目标(邮箱、飞书、Webhook)', + category: 'package', + auth: true, + price: 10, + params: [ + { name: 'endpoint', type: 'string', required: true, desc: '固定值: packages/export' }, + { name: 'package_id', type: 'string', required: true, desc: '流量包ID' }, + { name: 'target', type: 'string', required: true, desc: '导出目标: email/feishu/webhook' }, + { name: 'config', type: 'object', required: false, desc: '导出配置(邮箱地址、Webhook URL等)' }, + ], + response: `{ + "success": true, + "data": { + "package_id": "pkg_1706698200000", + "target": "email", + "status": "exporting", + "task_id": "export_1706698200000", + "estimatedTime": "2分钟" + }, + "credits_used": 10 +}`, + }, +] + +export default function APIDocsPage() { + const [activeCategory, setActiveCategory] = useState('all') + const [apiBaseUrl, setApiBaseUrl] = useState('') + const [copiedId, setCopiedId] = useState(null) + const [searchQuery, setSearchQuery] = useState('') + const [expandedEndpoints, setExpandedEndpoints] = useState>(new Set()) + + useEffect(() => { + const host = typeof window !== 'undefined' ? window.location.origin : '' + setApiBaseUrl(host) + }, []) + + const copyToClipboard = (text: string, id: string) => { + navigator.clipboard.writeText(text) + setCopiedId(id) + setTimeout(() => setCopiedId(null), 2000) + } + + const getMethodColor = (method: string) => { + switch (method) { + case 'GET': return 'bg-green-100 text-green-700 border-green-200' + case 'POST': return 'bg-blue-100 text-blue-700 border-blue-200' + case 'PUT': return 'bg-yellow-100 text-yellow-700 border-yellow-200' + case 'DELETE': return 'bg-red-100 text-red-700 border-red-200' + default: return 'bg-gray-100 text-gray-700 border-gray-200' + } + } + + const toggleEndpoint = (id: string) => { + const newSet = new Set(expandedEndpoints) + if (newSet.has(id)) { + newSet.delete(id) + } else { + newSet.add(id) + } + setExpandedEndpoints(newSet) + } + + const filteredEndpoints = API_ENDPOINTS.filter(e => { + const matchCategory = activeCategory === 'all' || e.category === activeCategory + const matchSearch = !searchQuery || + e.name.toLowerCase().includes(searchQuery.toLowerCase()) || + e.path.toLowerCase().includes(searchQuery.toLowerCase()) || + e.description.toLowerCase().includes(searchQuery.toLowerCase()) + return matchCategory && matchSearch + }) + + return ( +
+
+ {/* 顶部标题 */} +
+
+ + + +
+

API文档

+

神射手开放API完整文档 · 支持AI直接对接

+
+
+
+ v1.0 + + + +
+
+ + {/* AI对接提示卡片 */} + + +
+
+
+ +
+
+

AI直接对接

+

将以下链接提供给AI(如ChatGPT、Claude),即可直接理解并调用神射手API

+
+ {apiBaseUrl}/api/docs?format=openapi + +
+
+
+
+
+ OpenAPI 3.0 + 标准格式 +
+
+ Markdown + 可读文档 +
+
+
+
+
+ + {/* 快速入门 */} + + +
+
+ +
+
+

快速入门

+
+
+
+ 1 + 获取密钥 +
+

在密钥管理页面创建API密钥

+
+
+
+ 2 + 配置权限 +
+

设置可访问的数据字段

+
+
+
+ 3 + 开始调用 +
+

使用密钥调用API接口

+
+
+
+

基础请求示例

+
+{`curl -X GET "${apiBaseUrl}/api/shensheshou?endpoint=user&phone=13800138000" \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "X-API-Secret: YOUR_API_SECRET"`}
+                  
+
+
+
+
+
+ + {/* 搜索和分类 */} +
+
+ + setSearchQuery(e.target.value)} + /> +
+
+ + {API_CATEGORIES.map(cat => { + const count = API_ENDPOINTS.filter(e => e.category === cat.id).length + const Icon = cat.icon + return ( + + ) + })} +
+
+ + {/* API端点列表 */} +
+ {filteredEndpoints.map(endpoint => ( + +
toggleEndpoint(endpoint.id)} + > +
+
+ + {endpoint.method} + + {endpoint.path} + +
+
+ + + {endpoint.price} 信用点 + + {endpoint.auth && ( + + + 需认证 + + )} + {expandedEndpoints.has(endpoint.id) ? ( + + ) : ( + + )} +
+
+
+

{endpoint.name}

+

{endpoint.description}

+
+
+ + {/* 展开详情 */} + {expandedEndpoints.has(endpoint.id) && ( +
+ {/* 参数 */} + {endpoint.params && endpoint.params.length > 0 && ( +
+

请求参数

+
+ + + + + + + + + + + {endpoint.params.map((param, i) => ( + + + + + + + ))} + +
参数名类型必填说明
+ {param.name} + + {param.type} + + {param.required ? ( + 必填 + ) : ( + 可选 + )} + {param.desc}
+
+
+ )} + + {/* 调用示例 */} + {endpoint.example && ( +
+
+

调用示例

+ +
+
+                        {endpoint.example}
+                      
+
+ )} + + {/* 响应示例 */} + {endpoint.response && ( +
+
+

响应示例

+ 200 OK +
+
+                        {endpoint.response}
+                      
+
+ )} +
+ )} +
+ ))} +
+ + {/* 错误码说明 */} + + + 错误码说明 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
HTTP状态码错误类型说明
200成功请求成功处理
400请求错误参数缺失或格式错误
401认证失败API密钥无效或缺失
403权限不足无权访问该资源或字段
404未找到请求的资源不存在
429限流超出调用频率限制或信用点不足
500服务器错误服务器内部错误,请稍后重试
+
+
+
+
+ ) +} diff --git a/app/data-market/api/keys/page.tsx b/app/data-market/api/keys/page.tsx new file mode 100644 index 0000000..8201f89 --- /dev/null +++ b/app/data-market/api/keys/page.tsx @@ -0,0 +1,772 @@ +"use client" + +import { useState, useEffect } from "react" +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" +import { Input } from "@/components/ui/input" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Label } from "@/components/ui/label" +import { Checkbox } from "@/components/ui/checkbox" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { + Copy, + CheckCircle2, + Key, + RefreshCw, + Plus, + Trash2, + Eye, + EyeOff, + Settings, + Shield, + Lock, + Unlock, + ArrowLeft, + AlertTriangle, + Clock, + Activity, +} from "lucide-react" +import Link from "next/link" + +// ==================== 类型定义 ==================== + +interface FieldPermission { + fieldGroup: string + fields: { + name: string + label: string + enabled: boolean + price: number + }[] +} + +interface APIKey { + id: string + name: string + key: string + secret: string + status: 'active' | 'disabled' | 'expired' + createdAt: string + expiresAt: string | null + lastUsed: string | null + permissions: FieldPermission[] + rateLimit: { + requestsPerDay: number + requestsPerMonth: number + } + billing: { + plan: 'free' | 'basic' | 'pro' | 'enterprise' + usedCredits: number + totalCredits: number + } + callStats: { + today: number + thisMonth: number + total: number + } +} + +// 计费套餐 +const BILLING_PLANS = [ + { id: 'free', name: '免费版', price: 0, credits: 1000, requestsPerDay: 100, requestsPerMonth: 3000 }, + { id: 'basic', name: '基础版', price: 99, credits: 10000, requestsPerDay: 1000, requestsPerMonth: 30000 }, + { id: 'pro', name: '专业版', price: 299, credits: 50000, requestsPerDay: 5000, requestsPerMonth: 150000 }, + { id: 'enterprise', name: '企业版', price: 999, credits: -1, requestsPerDay: -1, requestsPerMonth: -1 }, +] + +// 字段分组及权限 +const FIELD_GROUPS: FieldPermission[] = [ + { + fieldGroup: '基础信息', + fields: [ + { name: 'phone', label: '手机号', enabled: true, price: 1 }, + { name: 'qq', label: 'QQ号', enabled: true, price: 1 }, + { name: 'wechat', label: '微信号', enabled: false, price: 2 }, + { name: 'email', label: '邮箱', enabled: false, price: 1 }, + { name: 'nickname', label: '昵称', enabled: true, price: 0.5 }, + ] + }, + { + fieldGroup: '用户画像', + fields: [ + { name: 'rfm_score', label: 'RFM评分', enabled: true, price: 2 }, + { name: 'user_level', label: '用户等级', enabled: true, price: 1 }, + { name: 'value_score', label: '价值评分', enabled: false, price: 3 }, + { name: 'activity_score', label: '活跃度评分', enabled: false, price: 2 }, + { name: 'loyalty_score', label: '忠诚度评分', enabled: false, price: 2 }, + ] + }, + { + fieldGroup: '标签数据', + fields: [ + { name: 'basic_tags', label: '基础标签', enabled: true, price: 1 }, + { name: 'behavior_tags', label: '行为标签', enabled: false, price: 2 }, + { name: 'preference_tags', label: '偏好标签', enabled: false, price: 2 }, + { name: 'ai_tags', label: 'AI智能标签', enabled: false, price: 5 }, + { name: 'custom_tags', label: '自定义标签', enabled: true, price: 1 }, + ] + }, + { + fieldGroup: '行为数据', + fields: [ + { name: 'last_active', label: '最后活跃时间', enabled: true, price: 0.5 }, + { name: 'visit_count', label: '访问次数', enabled: false, price: 1 }, + { name: 'purchase_history', label: '购买历史', enabled: false, price: 5 }, + { name: 'interaction_log', label: '交互记录', enabled: false, price: 3 }, + { name: 'channel_source', label: '渠道来源', enabled: true, price: 1 }, + ] + }, + { + fieldGroup: '扩展数据', + fields: [ + { name: 'social_bindings', label: '社交绑定', enabled: false, price: 3 }, + { name: 'device_info', label: '设备信息', enabled: false, price: 2 }, + { name: 'location_data', label: '位置数据', enabled: false, price: 4 }, + { name: 'risk_assessment', label: '风险评估', enabled: false, price: 5 }, + { name: 'ai_insights', label: 'AI洞察', enabled: false, price: 10 }, + ] + }, +] + +// 模拟API密钥数据 +const MOCK_API_KEYS: APIKey[] = [ + { + id: 'key_1', + name: '存客宝-生产环境', + key: 'sk-archer-ckb-prod-a1b2c3d4e5f6', + secret: 'sec-ckb-x9y8z7w6v5u4', + status: 'active', + createdAt: '2026-01-15', + expiresAt: null, + lastUsed: '2026-01-31 14:32:15', + permissions: JSON.parse(JSON.stringify(FIELD_GROUPS)), + rateLimit: { requestsPerDay: 5000, requestsPerMonth: 150000 }, + billing: { plan: 'pro', usedCredits: 12580, totalCredits: 50000 }, + callStats: { today: 342, thisMonth: 8956, total: 45678 } + }, + { + id: 'key_2', + name: '点了码-测试环境', + key: 'sk-archer-dlm-test-g7h8i9j0k1l2', + secret: 'sec-dlm-m3n4o5p6q7r8', + status: 'active', + createdAt: '2026-01-20', + expiresAt: '2026-04-20', + lastUsed: '2026-01-30 09:15:42', + permissions: JSON.parse(JSON.stringify(FIELD_GROUPS)).map((g: FieldPermission) => ({ + ...g, + fields: g.fields.map(f => ({ ...f, enabled: f.price <= 2 })) + })), + rateLimit: { requestsPerDay: 1000, requestsPerMonth: 30000 }, + billing: { plan: 'basic', usedCredits: 2340, totalCredits: 10000 }, + callStats: { today: 56, thisMonth: 1234, total: 5678 } + }, + { + id: 'key_3', + name: '内部测试密钥', + key: 'sk-archer-internal-s3t4u5v6w7x8', + secret: 'sec-int-y9z0a1b2c3d4', + status: 'disabled', + createdAt: '2026-01-10', + expiresAt: null, + lastUsed: '2026-01-25 16:45:30', + permissions: JSON.parse(JSON.stringify(FIELD_GROUPS)).map((g: FieldPermission) => ({ + ...g, + fields: g.fields.map(f => ({ ...f, enabled: true })) + })), + rateLimit: { requestsPerDay: -1, requestsPerMonth: -1 }, + billing: { plan: 'enterprise', usedCredits: 0, totalCredits: -1 }, + callStats: { today: 0, thisMonth: 567, total: 12345 } + }, +] + +export default function APIKeysPage() { + const [apiKeys, setApiKeys] = useState(MOCK_API_KEYS) + const [apiBaseUrl, setApiBaseUrl] = useState('') + + // 弹窗状态 + const [showCreateKeyDialog, setShowCreateKeyDialog] = useState(false) + const [showKeyDetailDialog, setShowKeyDetailDialog] = useState(false) + const [showPermissionDialog, setShowPermissionDialog] = useState(false) + const [selectedKey, setSelectedKey] = useState(null) + + // 复制状态 + const [copiedId, setCopiedId] = useState(null) + + // 新密钥表单 + const [newKeyForm, setNewKeyForm] = useState<{ + name: string + plan: 'free' | 'basic' | 'pro' | 'enterprise' + expiresAt: string + }>({ + name: '', + plan: 'basic', + expiresAt: '', + }) + + // 显示/隐藏密钥 + const [visibleKeys, setVisibleKeys] = useState>(new Set()) + + useEffect(() => { + const host = typeof window !== 'undefined' ? window.location.origin : '' + setApiBaseUrl(host) + }, []) + + const copyToClipboard = (text: string, id: string) => { + navigator.clipboard.writeText(text) + setCopiedId(id) + setTimeout(() => setCopiedId(null), 2000) + } + + const toggleKeyVisibility = (keyId: string) => { + const newSet = new Set(visibleKeys) + if (newSet.has(keyId)) { + newSet.delete(keyId) + } else { + newSet.add(keyId) + } + setVisibleKeys(newSet) + } + + const maskKey = (key: string) => { + return key.substring(0, 12) + '••••••••••••' + } + + const getStatusBadge = (status: string) => { + switch (status) { + case 'active': return 正常 + case 'disabled': return 已禁用 + case 'expired': return 已过期 + default: return {status} + } + } + + const getPlanBadge = (plan: string) => { + switch (plan) { + case 'free': return 免费版 + case 'basic': return 基础版 + case 'pro': return 专业版 + case 'enterprise': return 企业版 + default: return {plan} + } + } + + // 创建新密钥 + const handleCreateKey = () => { + const plan = BILLING_PLANS.find(p => p.id === newKeyForm.plan)! + const newKey: APIKey = { + id: `key_${Date.now()}`, + name: newKeyForm.name, + key: `sk-archer-${Math.random().toString(36).substring(2, 14)}`, + secret: `sec-${Math.random().toString(36).substring(2, 14)}`, + status: 'active', + createdAt: new Date().toISOString().split('T')[0], + expiresAt: newKeyForm.expiresAt || null, + lastUsed: null, + permissions: JSON.parse(JSON.stringify(FIELD_GROUPS)), + rateLimit: { requestsPerDay: plan.requestsPerDay, requestsPerMonth: plan.requestsPerMonth }, + billing: { plan: plan.id as 'free' | 'basic' | 'pro' | 'enterprise', usedCredits: 0, totalCredits: plan.credits }, + callStats: { today: 0, thisMonth: 0, total: 0 } + } + setApiKeys([...apiKeys, newKey]) + setShowCreateKeyDialog(false) + setNewKeyForm({ name: '', plan: 'basic', expiresAt: '' }) + } + + // 切换密钥状态 + const toggleKeyStatus = (keyId: string) => { + setApiKeys(apiKeys.map(k => + k.id === keyId + ? { ...k, status: k.status === 'active' ? 'disabled' : 'active' } + : k + )) + } + + // 删除密钥 + const deleteKey = (keyId: string) => { + if (confirm('确定要删除此API密钥吗?此操作不可恢复。')) { + setApiKeys(apiKeys.filter(k => k.id !== keyId)) + } + } + + // 更新字段权限 + const updateFieldPermission = (groupIndex: number, fieldIndex: number, enabled: boolean) => { + if (!selectedKey) return + const newPermissions = [...selectedKey.permissions] + newPermissions[groupIndex].fields[fieldIndex].enabled = enabled + setSelectedKey({ ...selectedKey, permissions: newPermissions }) + } + + // 保存权限设置 + const savePermissions = () => { + if (!selectedKey) return + setApiKeys(apiKeys.map(k => + k.id === selectedKey.id ? selectedKey : k + )) + setShowPermissionDialog(false) + } + + // 统计 + const activeKeysCount = apiKeys.filter(k => k.status === 'active').length + + return ( +
+
+ {/* 顶部标题 */} +
+
+ + + +
+

API密钥管理

+

创建和管理第三方系统接入密钥

+
+
+ +
+ + {/* 统计卡片 */} +
+ + +
+
+ +
+
+
{apiKeys.length}
+
总密钥数
+
+
+
+
+ + + +
+
+ +
+
+
{activeKeysCount}
+
正常使用
+
+
+
+
+ + + +
+
+ +
+
+
{apiKeys.filter(k => k.status === 'disabled').length}
+
已禁用
+
+
+
+
+ + + +
+
+ +
+
+
{apiKeys.reduce((sum, k) => sum + k.callStats.today, 0).toLocaleString()}
+
今日调用
+
+
+
+
+
+ + {/* 认证说明 */} + + +

认证方式

+

所有API请求需要在Header中携带API密钥和密钥Secret:

+
+{`curl -X GET "${apiBaseUrl}/api/shensheshou/user?phone=13800138000" \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "X-API-Secret: YOUR_API_SECRET" \\
+  -H "Content-Type: application/json"`}
+            
+
+
+ + {/* 密钥列表 */} + + + 密钥列表 + 管理所有API密钥,配置权限和额度 + + + + + + 名称 + API Key + 套餐 + 状态 + 今日/本月调用 + 信用点 + 最后使用 + 操作 + + + + {apiKeys.map(key => ( + + {key.name} + +
+ + {visibleKeys.has(key.id) ? key.key : maskKey(key.key)} + + + +
+
+ {getPlanBadge(key.billing.plan)} + {getStatusBadge(key.status)} + + {key.callStats.today.toLocaleString()} / {key.callStats.thisMonth.toLocaleString()} + + +
+
+
+
+ + {key.billing.totalCredits === -1 ? '无限' : `${key.billing.usedCredits}/${key.billing.totalCredits}`} + +
+ + + {key.lastUsed || '-'} + + +
+ + + + +
+
+ + ))} + +
+
+
+ + {/* ==================== 弹窗 ==================== */} + + {/* 创建密钥弹窗 */} + + + + 创建API密钥 + 为第三方系统创建新的API访问密钥 + +
+
+ + setNewKeyForm({ ...newKeyForm, name: e.target.value })} + /> +
+
+ + +
+
+ + setNewKeyForm({ ...newKeyForm, expiresAt: e.target.value })} + /> +

留空表示永不过期

+
+
+ + + + +
+
+ + {/* 密钥详情弹窗 */} + + + + 密钥详情 + {selectedKey?.name} + + {selectedKey && ( +
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+
+ +

{selectedKey.createdAt}

+
+
+ +

{selectedKey.expiresAt || '永不过期'}

+
+
+ +

{getPlanBadge(selectedKey.billing.plan)}

+
+
+ +

{getStatusBadge(selectedKey.status)}

+
+
+
+

调用统计

+
+
+
{selectedKey.callStats.today.toLocaleString()}
+
今日调用
+
+
+
{selectedKey.callStats.thisMonth.toLocaleString()}
+
本月调用
+
+
+
{selectedKey.callStats.total.toLocaleString()}
+
总调用
+
+
+
+
+ ⚠️ 请妥善保管API密钥,不要在客户端代码中暴露 +
+
+ )} + + + + +
+
+ + {/* 字段权限配置弹窗 */} + + + + 字段权限配置 + 配置 {selectedKey?.name} 可访问的数据字段 + + {selectedKey && ( +
+ {selectedKey.permissions.map((group, groupIndex) => ( +
+
+

{group.fieldGroup}

+ +
+
+ {group.fields.map((field, fieldIndex) => ( +
+
+ updateFieldPermission(groupIndex, fieldIndex, checked as boolean)} + /> +
+
{field.label}
+
{field.name}
+
+
+ + {field.price} 点/次 + +
+ ))} +
+
+ ))} +
+ 💡 调用API时,每访问一个字段都会按对应价格扣除信用点。未授权的字段在API响应中将被过滤。 +
+
+ )} + + + + +
+
+
+
+ ) +} diff --git a/app/data-market/api/page.tsx b/app/data-market/api/page.tsx index bc024b2..71a63f2 100644 --- a/app/data-market/api/page.tsx +++ b/app/data-market/api/page.tsx @@ -1,7 +1,7 @@ "use client" import { useState, useEffect } from "react" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { Input } from "@/components/ui/input" @@ -15,6 +15,23 @@ import { DialogTitle, } from "@/components/ui/dialog" import { Label } from "@/components/ui/label" +import { Switch } from "@/components/ui/switch" +import { Checkbox } from "@/components/ui/checkbox" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" import { Server, Copy, @@ -32,7 +49,67 @@ import { FileText, Package, Activity, + Plus, + Trash2, + Eye, + EyeOff, + Settings, + Shield, + CreditCard, + BarChart3, + Clock, + DollarSign, + AlertCircle, + CheckCircle, + XCircle, + ChevronDown, + ChevronRight, + Lock, + Unlock, + Download, + Filter, } from "lucide-react" +import Link from "next/link" + +// ==================== 类型定义 ==================== + +// API密钥接口 +interface APIKey { + id: string + name: string + key: string + secret: string + status: 'active' | 'disabled' | 'expired' + createdAt: string + expiresAt: string | null + lastUsed: string | null + permissions: FieldPermission[] + rateLimit: { + requestsPerDay: number + requestsPerMonth: number + } + billing: { + plan: 'free' | 'basic' | 'pro' | 'enterprise' + usedCredits: number + totalCredits: number + } + callStats: { + today: number + thisMonth: number + total: number + } +} + +// 字段权限接口 +interface FieldPermission { + fieldGroup: string + fields: { + name: string + label: string + enabled: boolean + price: number // 每次调用价格(信用点) + }[] +} // API端点接口 interface APIEndpoint { @@ -43,10 +120,131 @@ interface APIEndpoint { description: string category: string auth: boolean + price: number // 每次调用价格(信用点) params?: { name: string; type: string; required: boolean; desc: string }[] response?: string + example?: string } +// 计费套餐 +interface BillingPlan { + id: 'free' | 'basic' | 'pro' | 'enterprise' + name: string + price: number + credits: number + features: string[] + rateLimit: { + requestsPerDay: number + requestsPerMonth: number + } +} + +// 调用日志 +interface CallLog { + id: string + keyId: string + keyName: string + endpoint: string + method: string + status: number + credits: number + responseTime: number + timestamp: string + ip: string +} + +// ==================== 常量数据 ==================== + +// 计费套餐 +const BILLING_PLANS: BillingPlan[] = [ + { + id: 'free', + name: '免费版', + price: 0, + credits: 1000, + features: ['每日100次调用', '基础字段访问', '标准响应速度'], + rateLimit: { requestsPerDay: 100, requestsPerMonth: 3000 } + }, + { + id: 'basic', + name: '基础版', + price: 99, + credits: 10000, + features: ['每日1000次调用', '全部字段访问', '优先响应'], + rateLimit: { requestsPerDay: 1000, requestsPerMonth: 30000 } + }, + { + id: 'pro', + name: '专业版', + price: 299, + credits: 50000, + features: ['每日5000次调用', '全部字段+AI分析', '实时响应', '专属支持'], + rateLimit: { requestsPerDay: 5000, requestsPerMonth: 150000 } + }, + { + id: 'enterprise', + name: '企业版', + price: 999, + credits: -1, // 无限 + features: ['无限调用', '全部功能', '定制开发', '7x24支持'], + rateLimit: { requestsPerDay: -1, requestsPerMonth: -1 } + }, +] + +// 字段分组及权限 +const FIELD_GROUPS: FieldPermission[] = [ + { + fieldGroup: '基础信息', + fields: [ + { name: 'phone', label: '手机号', enabled: true, price: 1 }, + { name: 'qq', label: 'QQ号', enabled: true, price: 1 }, + { name: 'wechat', label: '微信号', enabled: false, price: 2 }, + { name: 'email', label: '邮箱', enabled: false, price: 1 }, + { name: 'nickname', label: '昵称', enabled: true, price: 0.5 }, + ] + }, + { + fieldGroup: '用户画像', + fields: [ + { name: 'rfm_score', label: 'RFM评分', enabled: true, price: 2 }, + { name: 'user_level', label: '用户等级', enabled: true, price: 1 }, + { name: 'value_score', label: '价值评分', enabled: false, price: 3 }, + { name: 'activity_score', label: '活跃度评分', enabled: false, price: 2 }, + { name: 'loyalty_score', label: '忠诚度评分', enabled: false, price: 2 }, + ] + }, + { + fieldGroup: '标签数据', + fields: [ + { name: 'basic_tags', label: '基础标签', enabled: true, price: 1 }, + { name: 'behavior_tags', label: '行为标签', enabled: false, price: 2 }, + { name: 'preference_tags', label: '偏好标签', enabled: false, price: 2 }, + { name: 'ai_tags', label: 'AI智能标签', enabled: false, price: 5 }, + { name: 'custom_tags', label: '自定义标签', enabled: true, price: 1 }, + ] + }, + { + fieldGroup: '行为数据', + fields: [ + { name: 'last_active', label: '最后活跃时间', enabled: true, price: 0.5 }, + { name: 'visit_count', label: '访问次数', enabled: false, price: 1 }, + { name: 'purchase_history', label: '购买历史', enabled: false, price: 5 }, + { name: 'interaction_log', label: '交互记录', enabled: false, price: 3 }, + { name: 'channel_source', label: '渠道来源', enabled: true, price: 1 }, + ] + }, + { + fieldGroup: '扩展数据', + fields: [ + { name: 'social_bindigns', label: '社交绑定', enabled: false, price: 3 }, + { name: 'device_info', label: '设备信息', enabled: false, price: 2 }, + { name: 'location_data', label: '位置数据', enabled: false, price: 4 }, + { name: 'risk_assessment', label: '风险评估', enabled: false, price: 5 }, + { name: 'ai_insights', label: 'AI洞察', enabled: false, price: 10 }, + ] + }, +] + // API分类 const API_CATEGORIES = [ { id: 'query', name: '用户查询', icon: Search }, @@ -65,27 +263,55 @@ const API_ENDPOINTS: APIEndpoint[] = [ method: 'GET', path: '/api/shensheshou/user', name: '用户画像查询', - description: '根据手机号或QQ查询完整用户画像', + description: '根据手机号或QQ查询完整用户画像,返回字段根据API密钥权限决定', category: 'query', auth: true, + price: 1, params: [ { name: 'phone', type: 'string', required: false, desc: '11位手机号' }, { name: 'qq', type: 'string', required: false, desc: 'QQ号码' }, + { name: 'fields', type: 'string', required: false, desc: '指定返回字段,逗号分隔' }, ], - response: '{ "user": { "phone": "138xxx", "rfm": 85, "level": "A", "tags": [...] } }', + response: `{ + "success": true, + "data": { + "phone": "138****8000", + "rfm_score": 85, + "user_level": "A", + "tags": ["高价值", "活跃用户"], + "last_active": "2026-01-30" + }, + "credits_used": 5, + "credits_remaining": 995 +}`, + example: `curl -X GET "https://api.shensheshou.com/api/shensheshou/user?phone=13800138000&fields=rfm_score,tags" \\ + -H "Authorization: Bearer sk-archer-xxxxx" \\ + -H "X-API-Secret: sec-xxxxx"`, }, { id: 'api_2', method: 'POST', path: '/api/shensheshou/users/batch', name: '批量用户查询', - description: '批量查询多个用户的画像信息', + description: '批量查询多个用户的画像信息,最多支持100个用户/次', category: 'query', auth: true, + price: 0.8, // 批量折扣 params: [ - { name: 'phones', type: 'array', required: false, desc: '手机号数组' }, - { name: 'qqs', type: 'array', required: false, desc: 'QQ号数组' }, + { name: 'phones', type: 'array', required: false, desc: '手机号数组,最多100个' }, + { name: 'qqs', type: 'array', required: false, desc: 'QQ号数组,最多100个' }, + { name: 'fields', type: 'array', required: false, desc: '指定返回字段数组' }, ], + response: `{ + "success": true, + "data": [ + { "phone": "138****8000", "rfm_score": 85, "user_level": "A" }, + { "phone": "139****9000", "rfm_score": 72, "user_level": "B" } + ], + "total": 2, + "credits_used": 8, + "credits_remaining": 992 +}`, }, // 标签服务 { @@ -93,11 +319,12 @@ const API_ENDPOINTS: APIEndpoint[] = [ method: 'GET', path: '/api/shensheshou/tags', name: '标签列表', - description: '获取所有可用标签', + description: '获取系统中所有可用标签及其分类', category: 'tag', auth: true, + price: 0.5, params: [ - { name: 'category', type: 'string', required: false, desc: '标签分类' }, + { name: 'category', type: 'string', required: false, desc: '标签分类筛选' }, ], }, { @@ -105,39 +332,54 @@ const API_ENDPOINTS: APIEndpoint[] = [ method: 'POST', path: '/api/shensheshou/tags/apply', name: '应用标签', - description: '为用户应用指定标签', + description: '为指定用户批量应用标签', category: 'tag', auth: true, + price: 2, params: [ - { name: 'userId', type: 'string', required: true, desc: '用户ID' }, - { name: 'tags', type: 'array', required: true, desc: '标签ID数组' }, + { name: 'user_id', type: 'string', required: true, desc: '用户ID或手机号' }, + { name: 'tags', type: 'array', required: true, desc: '要应用的标签ID数组' }, + { name: 'overwrite', type: 'boolean', required: false, desc: '是否覆盖现有标签' }, ], }, // AI服务 { id: 'api_5', method: 'POST', - path: '/api/shensheshou/chat', - name: 'AI对话', - description: '与神射手AI进行对话,支持自然语言查询', + path: '/api/shensheshou/ai/chat', + name: 'AI智能对话', + description: '与神射手AI进行对话,支持自然语言查询和数据分析', category: 'ai', auth: true, + price: 5, params: [ { name: 'message', type: 'string', required: true, desc: '对话内容' }, + { name: 'context', type: 'object', required: false, desc: '上下文信息' }, + { name: 'model', type: 'string', required: false, desc: 'AI模型: qwen/deepseek' }, ], - response: '{ "success": true, "response": { "content": "查询结果...", "data": {...} } }', + response: `{ + "success": true, + "response": { + "content": "根据查询,共有 1,234 位高价值用户...", + "data": { "count": 1234, "avg_rfm": 82 }, + "suggestions": ["可以进一步筛选活跃度", "建议导出为流量包"] + }, + "credits_used": 5 +}`, }, { id: 'api_6', method: 'POST', path: '/api/shensheshou/ai/analyze', name: 'AI数据分析', - description: 'AI自动分析数据并生成洞察', + description: 'AI自动分析用户群体特征并生成洞察报告', category: 'ai', auth: true, + price: 10, params: [ - { name: 'type', type: 'string', required: true, desc: '分析类型: rfm/behavior/preference' }, - { name: 'filters', type: 'object', required: false, desc: '筛选条件' }, + { name: 'type', type: 'string', required: true, desc: '分析类型: rfm/behavior/preference/churn' }, + { name: 'filters', type: 'object', required: false, desc: '用户筛选条件' }, + { name: 'depth', type: 'string', required: false, desc: '分析深度: quick/standard/deep' }, ], }, { @@ -145,12 +387,14 @@ const API_ENDPOINTS: APIEndpoint[] = [ method: 'POST', path: '/api/shensheshou/ai/tag', name: 'AI智能打标', - description: 'AI自动为用户打标签', + description: 'AI自动分析用户数据并智能打标签', category: 'ai', auth: true, + price: 8, params: [ - { name: 'source', type: 'string', required: true, desc: '数据源' }, - { name: 'model', type: 'string', required: false, desc: 'AI模型: qwen/deepseek' }, + { name: 'user_ids', type: 'array', required: true, desc: '用户ID数组' }, + { name: 'tag_types', type: 'array', required: false, desc: '指定标签类型' }, + { name: 'model', type: 'string', required: false, desc: 'AI模型选择' }, ], }, // 数据服务 @@ -159,21 +403,24 @@ const API_ENDPOINTS: APIEndpoint[] = [ method: 'GET', path: '/api/shensheshou/sources', name: '数据源列表', - description: '获取所有数据源连接状态', + description: '获取所有已接入的数据源及其状态', category: 'data', auth: true, + price: 0.5, }, { id: 'api_9', method: 'POST', path: '/api/shensheshou/ingest', name: '数据导入', - description: '导入外部数据并通过AI标签引擎处理', + description: '导入外部数据到神射手平台,自动触发AI标签引擎处理', category: 'data', auth: true, + price: 3, params: [ { name: 'source', type: 'string', required: true, desc: '数据源标识' }, - { name: 'target', type: 'string', required: true, desc: '目标表' }, + { name: 'data', type: 'array', required: true, desc: '用户数据数组' }, + { name: 'auto_tag', type: 'boolean', required: false, desc: '是否自动打标' }, ], }, // 报告服务 @@ -181,13 +428,15 @@ const API_ENDPOINTS: APIEndpoint[] = [ id: 'api_10', method: 'POST', path: '/api/shensheshou/report/generate', - name: '生成报告', - description: 'AI自动生成数据分析报告', + name: '生成分析报告', + description: 'AI自动生成数据分析报告,支持多种模板', category: 'report', auth: true, + price: 15, params: [ - { name: 'template', type: 'string', required: true, desc: '报告模板ID' }, - { name: 'dateRange', type: 'string', required: false, desc: '日期范围' }, + { name: 'template', type: 'string', required: true, desc: '报告模板: user_insight/rfm_analysis/trend_report' }, + { name: 'date_range', type: 'object', required: false, desc: '日期范围 {start, end}' }, + { name: 'format', type: 'string', required: false, desc: '输出格式: pdf/html/json' }, ], }, // 流量包 @@ -196,43 +445,139 @@ const API_ENDPOINTS: APIEndpoint[] = [ method: 'GET', path: '/api/shensheshou/packages', name: '流量包列表', - description: '获取所有流量包', + description: '获取所有已创建的流量包', category: 'package', auth: true, + price: 0.5, }, { id: 'api_12', method: 'POST', path: '/api/shensheshou/packages/create', name: '创建流量包', - description: '根据筛选条件创建流量包', + description: '根据筛选条件创建用户流量包', category: 'package', auth: true, + price: 5, params: [ { name: 'name', type: 'string', required: true, desc: '流量包名称' }, { name: 'criteria', type: 'object', required: true, desc: '筛选条件' }, + { name: 'export_fields', type: 'array', required: false, desc: '导出字段' }, ], }, { id: 'api_13', method: 'POST', - path: '/api/shensheshou/packages/send', - name: '发送流量包', - description: '发送流量包到邮箱/飞书/微信', + path: '/api/shensheshou/packages/export', + name: '导出流量包', + description: '导出流量包数据到指定目标', category: 'package', auth: true, + price: 10, params: [ - { name: 'packageId', type: 'string', required: true, desc: '流量包ID' }, - { name: 'targets', type: 'array', required: true, desc: '发送目标' }, + { name: 'package_id', type: 'string', required: true, desc: '流量包ID' }, + { name: 'target', type: 'string', required: true, desc: '导出目标: email/feishu/webhook' }, + { name: 'config', type: 'object', required: false, desc: '导出配置' }, ], }, ] +// 模拟API密钥数据 +const MOCK_API_KEYS: APIKey[] = [ + { + id: 'key_1', + name: '存客宝-生产环境', + key: 'sk-archer-ckb-prod-a1b2c3d4e5f6', + secret: 'sec-ckb-x9y8z7w6v5u4', + status: 'active', + createdAt: '2026-01-15', + expiresAt: null, + lastUsed: '2026-01-31 14:32:15', + permissions: JSON.parse(JSON.stringify(FIELD_GROUPS)), + rateLimit: { requestsPerDay: 5000, requestsPerMonth: 150000 }, + billing: { plan: 'pro', usedCredits: 12580, totalCredits: 50000 }, + callStats: { today: 342, thisMonth: 8956, total: 45678 } + }, + { + id: 'key_2', + name: '点了码-测试环境', + key: 'sk-archer-dlm-test-g7h8i9j0k1l2', + secret: 'sec-dlm-m3n4o5p6q7r8', + status: 'active', + createdAt: '2026-01-20', + expiresAt: '2026-04-20', + lastUsed: '2026-01-30 09:15:42', + permissions: JSON.parse(JSON.stringify(FIELD_GROUPS)).map((g: FieldPermission) => ({ + ...g, + fields: g.fields.map(f => ({ ...f, enabled: f.price <= 2 })) + })), + rateLimit: { requestsPerDay: 1000, requestsPerMonth: 30000 }, + billing: { plan: 'basic', usedCredits: 2340, totalCredits: 10000 }, + callStats: { today: 56, thisMonth: 1234, total: 5678 } + }, + { + id: 'key_3', + name: '内部测试密钥', + key: 'sk-archer-internal-s3t4u5v6w7x8', + secret: 'sec-int-y9z0a1b2c3d4', + status: 'disabled', + createdAt: '2026-01-10', + expiresAt: null, + lastUsed: '2026-01-25 16:45:30', + permissions: JSON.parse(JSON.stringify(FIELD_GROUPS)).map((g: FieldPermission) => ({ + ...g, + fields: g.fields.map(f => ({ ...f, enabled: true })) + })), + rateLimit: { requestsPerDay: -1, requestsPerMonth: -1 }, + billing: { plan: 'enterprise', usedCredits: 0, totalCredits: -1 }, + callStats: { today: 0, thisMonth: 567, total: 12345 } + }, +] + +// 模拟调用日志 +const MOCK_CALL_LOGS: CallLog[] = [ + { id: 'log_1', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/user', method: 'GET', status: 200, credits: 5, responseTime: 123, timestamp: '2026-01-31 14:32:15', ip: '123.45.67.89' }, + { id: 'log_2', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/users/batch', method: 'POST', status: 200, credits: 40, responseTime: 856, timestamp: '2026-01-31 14:30:02', ip: '123.45.67.89' }, + { id: 'log_3', keyId: 'key_2', keyName: '点了码-测试环境', endpoint: '/api/shensheshou/ai/chat', method: 'POST', status: 200, credits: 5, responseTime: 2341, timestamp: '2026-01-31 14:28:45', ip: '98.76.54.32' }, + { id: 'log_4', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/tags', method: 'GET', status: 200, credits: 0.5, responseTime: 45, timestamp: '2026-01-31 14:25:18', ip: '123.45.67.89' }, + { id: 'log_5', keyId: 'key_2', keyName: '点了码-测试环境', endpoint: '/api/shensheshou/user', method: 'GET', status: 403, credits: 0, responseTime: 12, timestamp: '2026-01-31 14:20:33', ip: '98.76.54.32' }, + { id: 'log_6', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/ai/analyze', method: 'POST', status: 200, credits: 10, responseTime: 5623, timestamp: '2026-01-31 14:15:00', ip: '123.45.67.89' }, + { id: 'log_7', keyId: 'key_1', keyName: '存客宝-生产环境', endpoint: '/api/shensheshou/packages/create', method: 'POST', status: 200, credits: 5, responseTime: 1234, timestamp: '2026-01-31 14:10:22', ip: '123.45.67.89' }, + { id: 'log_8', keyId: 'key_2', keyName: '点了码-测试环境', endpoint: '/api/shensheshou/ingest', method: 'POST', status: 429, credits: 0, responseTime: 8, timestamp: '2026-01-31 14:05:11', ip: '98.76.54.32' }, +] + +// ==================== 主组件 ==================== + export default function APIServicePage() { + const [activeTab, setActiveTab] = useState('keys') const [activeCategory, setActiveCategory] = useState('all') const [apiBaseUrl, setApiBaseUrl] = useState('') - const [showKeyDialog, setShowKeyDialog] = useState(false) + const [apiKeys, setApiKeys] = useState(MOCK_API_KEYS) + const [callLogs] = useState(MOCK_CALL_LOGS) + + // 弹窗状态 + const [showCreateKeyDialog, setShowCreateKeyDialog] = useState(false) + const [showKeyDetailDialog, setShowKeyDetailDialog] = useState(false) + const [showPermissionDialog, setShowPermissionDialog] = useState(false) + const [showBillingDialog, setShowBillingDialog] = useState(false) + const [selectedKey, setSelectedKey] = useState(null) + + // 复制状态 const [copiedId, setCopiedId] = useState(null) + + // 新密钥表单 + const [newKeyForm, setNewKeyForm] = useState<{ + name: string + plan: 'free' | 'basic' | 'pro' | 'enterprise' + expiresAt: string + }>({ + name: '', + plan: 'basic', + expiresAt: '', + }) + + // 显示/隐藏密钥 + const [visibleKeys, setVisibleKeys] = useState>(new Set()) useEffect(() => { const host = typeof window !== 'undefined' ? window.location.origin : '' @@ -245,6 +590,20 @@ export default function APIServicePage() { setTimeout(() => setCopiedId(null), 2000) } + const toggleKeyVisibility = (keyId: string) => { + const newSet = new Set(visibleKeys) + if (newSet.has(keyId)) { + newSet.delete(keyId) + } else { + newSet.add(keyId) + } + setVisibleKeys(newSet) + } + + const maskKey = (key: string) => { + return key.substring(0, 12) + '••••••••••••' + } + const getMethodColor = (method: string) => { switch (method) { case 'GET': return 'bg-green-100 text-green-700' @@ -255,10 +614,89 @@ export default function APIServicePage() { } } + const getStatusBadge = (status: string) => { + switch (status) { + case 'active': return 正常 + case 'disabled': return 已禁用 + case 'expired': return 已过期 + default: return {status} + } + } + + const getPlanBadge = (plan: string) => { + switch (plan) { + case 'free': return 免费版 + case 'basic': return 基础版 + case 'pro': return 专业版 + case 'enterprise': return 企业版 + default: return {plan} + } + } + const filteredEndpoints = activeCategory === 'all' ? API_ENDPOINTS : API_ENDPOINTS.filter(e => e.category === activeCategory) + // 创建新密钥 + const handleCreateKey = () => { + const plan = BILLING_PLANS.find(p => p.id === newKeyForm.plan)! + const newKey: APIKey = { + id: `key_${Date.now()}`, + name: newKeyForm.name, + key: `sk-archer-${Math.random().toString(36).substring(2, 14)}`, + secret: `sec-${Math.random().toString(36).substring(2, 14)}`, + status: 'active', + createdAt: new Date().toISOString().split('T')[0], + expiresAt: newKeyForm.expiresAt || null, + lastUsed: null, + permissions: JSON.parse(JSON.stringify(FIELD_GROUPS)), + rateLimit: plan.rateLimit, + billing: { plan: plan.id, usedCredits: 0, totalCredits: plan.credits }, + callStats: { today: 0, thisMonth: 0, total: 0 } + } + setApiKeys([...apiKeys, newKey]) + setShowCreateKeyDialog(false) + setNewKeyForm({ name: '', plan: 'basic', expiresAt: '' }) + } + + // 切换密钥状态 + const toggleKeyStatus = (keyId: string) => { + setApiKeys(apiKeys.map(k => + k.id === keyId + ? { ...k, status: k.status === 'active' ? 'disabled' : 'active' } + : k + )) + } + + // 删除密钥 + const deleteKey = (keyId: string) => { + if (confirm('确定要删除此API密钥吗?此操作不可恢复。')) { + setApiKeys(apiKeys.filter(k => k.id !== keyId)) + } + } + + // 更新字段权限 + const updateFieldPermission = (groupIndex: number, fieldIndex: number, enabled: boolean) => { + if (!selectedKey) return + const newPermissions = [...selectedKey.permissions] + newPermissions[groupIndex].fields[fieldIndex].enabled = enabled + setSelectedKey({ ...selectedKey, permissions: newPermissions }) + } + + // 保存权限设置 + const savePermissions = () => { + if (!selectedKey) return + setApiKeys(apiKeys.map(k => + k.id === selectedKey.id ? selectedKey : k + )) + setShowPermissionDialog(false) + } + + // 统计数据 + const totalCalls = apiKeys.reduce((sum, k) => sum + k.callStats.today, 0) + const totalCreditsUsed = apiKeys.reduce((sum, k) => sum + k.billing.usedCredits, 0) + const activeKeysCount = apiKeys.filter(k => k.status === 'active').length + return (
@@ -266,179 +704,736 @@ export default function APIServicePage() {

API服务

-

神射手开放API,支持第三方系统集成

+

神射手开放API,支持第三方系统集成与数据调用

- - + + + + + +
- {/* API基础信息 */} - - -
-
-

API基础地址

-
- - {apiBaseUrl || 'https://your-domain.com'} - - + {/* 统计卡片 */} +
+ + +
+
+ +
+
+
{apiKeys.length}
+
API密钥
-
-
API版本
- v1.0 +
{activeKeysCount} 个正常使用中
+ + + + + +
+
+ +
+
+
{totalCalls.toLocaleString()}
+
今日调用
+
-
-
-
- - {/* 认证说明 */} - - - 认证方式 - - -
-

所有API请求需要在Header中携带API密钥:

-
-{`curl -X GET "${apiBaseUrl}/api/shensheshou/user?phone=13800138000" \\
-  -H "Authorization: Bearer YOUR_API_KEY" \\
-  -H "Content-Type: application/json"`}
-              
-
-
-
- - {/* API分类筛选 */} -
- - {API_CATEGORIES.map(cat => { - const count = API_ENDPOINTS.filter(e => e.category === cat.id).length - const Icon = cat.icon - return ( - - ) - })} +
↑ 12.5% 较昨日
+ + + + + +
+
+ +
+
+
{totalCreditsUsed.toLocaleString()}
+
已用信用点
+
+
+
本月累计消耗
+
+
+ + + +
+
+ +
+
+
{API_ENDPOINTS.length}
+
API端点
+
+
+
{API_CATEGORIES.length} 个分类
+
+
- {/* API端点列表 */} -
- {filteredEndpoints.map(endpoint => ( - + {/* 主内容区 */} + + + + + 密钥管理 + + + + API文档 + + + + 调用日志 + + + + 计费明细 + + + + {/* 密钥管理 */} + + {/* API基础信息 */} + -
-
- - {endpoint.method} - - {endpoint.path} - -
- {endpoint.auth && ( - - - 需认证 - - )} -
- -

{endpoint.name}

-

{endpoint.description}

- - {/* 参数 */} - {endpoint.params && endpoint.params.length > 0 && ( -
-

参数

-
- {endpoint.params.map((param, i) => ( -
- {param.name} - {param.type} - {param.required && 必填} - {param.desc} -
- ))} +
+
+

API基础地址

+
+ + {apiBaseUrl || 'https://your-domain.com'} + +
- )} - - {/* 响应示例 */} - {endpoint.response && ( -
-

响应示例

-
-                      {endpoint.response}
-                    
+
+
API版本
+ v1.0
- )} +
- ))} -
- {/* API密钥弹窗 */} - - + {/* 密钥列表 */} + + +
+
+ API密钥列表 + 管理第三方系统接入密钥,配置字段权限和调用额度 +
+
+
+ + + + + 名称 + API Key + 套餐 + 状态 + 今日/本月调用 + 信用点 + 最后使用 + 操作 + + + + {apiKeys.map(key => ( + + {key.name} + +
+ + {visibleKeys.has(key.id) ? key.key : maskKey(key.key)} + + + +
+
+ {getPlanBadge(key.billing.plan)} + {getStatusBadge(key.status)} + + {key.callStats.today.toLocaleString()} / {key.callStats.thisMonth.toLocaleString()} + + +
+
+
+
+ + {key.billing.totalCredits === -1 ? '无限' : `${key.billing.usedCredits}/${key.billing.totalCredits}`} + +
+ + + {key.lastUsed || '-'} + + +
+ + + + +
+
+ + ))} + +
+
+
+ + {/* 认证说明 */} + + + 认证方式 + + +
+

所有API请求需要在Header中携带API密钥和密钥Secret:

+
+{`curl -X GET "${apiBaseUrl}/api/shensheshou/user?phone=13800138000" \\
+  -H "Authorization: Bearer YOUR_API_KEY" \\
+  -H "X-API-Secret: YOUR_API_SECRET" \\
+  -H "Content-Type: application/json"`}
+                  
+
+
+
+ + + {/* API文档 */} + + {/* API分类筛选 */} +
+ + {API_CATEGORIES.map(cat => { + const count = API_ENDPOINTS.filter(e => e.category === cat.id).length + const Icon = cat.icon + return ( + + ) + })} +
+ + {/* API端点列表 */} +
+ {filteredEndpoints.map(endpoint => ( + + +
+
+ + {endpoint.method} + + {endpoint.path} + +
+
+ + + {endpoint.price} 信用点/次 + + {endpoint.auth && ( + + + 需认证 + + )} +
+
+ +

{endpoint.name}

+

{endpoint.description}

+ + {/* 参数 */} + {endpoint.params && endpoint.params.length > 0 && ( +
+

请求参数

+
+ {endpoint.params.map((param, i) => ( +
+ {param.name} + {param.type} + {param.required && 必填} + {param.desc} +
+ ))} +
+
+ )} + + {/* 调用示例 */} + {endpoint.example && ( +
+

调用示例

+
+                          {endpoint.example}
+                        
+
+ )} + + {/* 响应示例 */} + {endpoint.response && ( +
+

响应示例

+
+                          {endpoint.response}
+                        
+
+ )} +
+
+ ))} +
+
+ + {/* 调用日志 */} + + + +
+
+ 调用日志 + 查看API调用记录和状态 +
+
+ + +
+
+
+ + + + + 时间 + 密钥名称 + 接口 + 方法 + 状态 + 信用点 + 响应时间 + IP地址 + + + + {callLogs.map(log => ( + + {log.timestamp} + {log.keyName} + + {log.endpoint} + + + {log.method} + + + {log.status === 200 ? ( + {log.status} + ) : log.status === 403 ? ( + {log.status} + ) : log.status === 429 ? ( + {log.status} + ) : ( + {log.status} + )} + + {log.credits > 0 ? log.credits : '-'} + {log.responseTime}ms + {log.ip} + + ))} + +
+
+
+
+ + {/* 计费明细 */} + + {/* 套餐对比 */} +
+ {BILLING_PLANS.map(plan => ( + + + {plan.id === 'pro' && ( + 推荐 + )} +

{plan.name}

+
+ ¥{plan.price} + /月 +
+
+ {plan.credits === -1 ? '无限信用点' : `${plan.credits.toLocaleString()} 信用点/月`} +
+
    + {plan.features.map((f, i) => ( +
  • + + {f} +
  • + ))} +
+ +
+
+ ))} +
+ + {/* 消费明细 */} + + + 本月消费明细 + + + + + + API分类 + 调用次数 + 单价 + 消耗信用点 + 占比 + + + + + 用户查询 + 5,234 + 1 信用点/次 + 5,234 + +
+
+
+
+ 45% +
+ + + + AI服务 + 423 + 5-10 信用点/次 + 3,156 + +
+
+
+
+ 27% +
+ + + + 标签服务 + 1,567 + 0.5-2 信用点/次 + 1,890 + +
+
+
+
+ 16% +
+ + + + 数据服务 + 234 + 0.5-3 信用点/次 + 456 + +
+
+
+
+ 4% +
+ + + + 流量包 + 89 + 5-10 信用点/次 + 844 + +
+
+
+
+ 7% +
+ + + +
+
+ 总计 + 11,580 信用点 +
+
+
+
+ + + {/* ==================== 弹窗 ==================== */} + + {/* 创建密钥弹窗 */} + + - API密钥管理 - 用于第三方系统调用神射手API + 创建API密钥 + 为第三方系统创建新的API访问密钥
- -
- - -
-

密钥创建于 2024-01-15,永不过期

+ + setNewKeyForm({ ...newKeyForm, name: e.target.value })} + />
-
- ⚠️ 请妥善保管API密钥,不要在客户端代码中暴露 +
+ + +
+
+ + setNewKeyForm({ ...newKeyForm, expiresAt: e.target.value })} + /> +

留空表示永不过期

- + + + + +
+ + {/* 密钥详情弹窗 */} + + + + 密钥详情 + {selectedKey?.name} + + {selectedKey && ( +
+
+ +
+ + + +
+
+
+ +
+ + + +
+
+
+
+ +

{selectedKey.createdAt}

+
+
+ +

{selectedKey.expiresAt || '永不过期'}

+
+
+ +

{getPlanBadge(selectedKey.billing.plan)}

+
+
+ +

{getStatusBadge(selectedKey.status)}

+
+
+
+

调用统计

+
+
+
{selectedKey.callStats.today.toLocaleString()}
+
今日调用
+
+
+
{selectedKey.callStats.thisMonth.toLocaleString()}
+
本月调用
+
+
+
{selectedKey.callStats.total.toLocaleString()}
+
总调用
+
+
+
+
+ ⚠️ 请妥善保管API密钥,不要在客户端代码中暴露 +
+
+ )} + +
+ + {/* 字段权限配置弹窗 */} + + + + 字段权限配置 + 配置 {selectedKey?.name} 可访问的数据字段 + + {selectedKey && ( +
+ {selectedKey.permissions.map((group, groupIndex) => ( +
+
+

{group.fieldGroup}

+ +
+
+ {group.fields.map((field, fieldIndex) => ( +
+
+ updateFieldPermission(groupIndex, fieldIndex, checked as boolean)} + /> +
+
{field.label}
+
{field.name}
+
+
+ + {field.price} 点/次 + +
+ ))} +
+
+ ))} +
+ 💡 调用API时,每访问一个字段都会按对应价格扣除信用点。未授权的字段在API响应中将被过滤。 +
+
+ )} + + + + +
+
+ + {/* 计费套餐弹窗 */} + + + + 计费套餐 + 选择适合您业务需求的API调用套餐 + +
+ {BILLING_PLANS.map(plan => ( + + + {plan.id === 'pro' && ( + 推荐 + )} +

{plan.name}

+
+ ¥{plan.price} + /月 +
+
+ {plan.credits === -1 ? '无限信用点' : `${plan.credits.toLocaleString()} 信用点/月`} +
+
    + {plan.features.map((f, i) => ( +
  • + + {f} +
  • + ))} +
+
+
每日限额: {plan.rateLimit.requestsPerDay === -1 ? '无限' : plan.rateLimit.requestsPerDay.toLocaleString()}
+
每月限额: {plan.rateLimit.requestsPerMonth === -1 ? '无限' : plan.rateLimit.requestsPerMonth.toLocaleString()}
+
+ +
+
+ ))} +
+ + + +
+
) diff --git a/app/data-market/open-api/page.tsx b/app/data-market/open-api/page.tsx index 42135f7..e6e9a32 100644 --- a/app/data-market/open-api/page.tsx +++ b/app/data-market/open-api/page.tsx @@ -81,16 +81,34 @@ interface Partner { tagEnrich: boolean // 标签完善权限 batchProcess: boolean // 批量处理权限 } + fieldPermissions: { // 字段级权限 + phone: boolean + qq: boolean + wechat: boolean + rfm_score: boolean + user_level: boolean + tags: boolean + behavior: boolean + location: boolean + } quotaConfig: { dailyLimit: number // 每日限额 monthlyLimit: number // 每月限额 rateLimit: number // 速率限制(次/秒) } + webhookConfig?: { // Webhook配置 + url: string + events: string[] // 订阅事件:data_synced, tag_enriched, task_completed + secret: string + enabled: boolean + } statistics: { totalCalls: number todayCalls: number monthCalls: number lastCallTime: string + successRate: number + avgResponseTime: number } createdAt: string updatedAt: string @@ -134,8 +152,8 @@ const DEFAULT_PARTNERS: Partner[] = [ name: '存客宝', type: 'external', description: '私域流量管理系统,提供流量词上报和用户画像获取', - apiKey: 'sk-ckb-xxxxxxxxxx', - apiSecret: 'sec-ckb-xxxxxxxxxx', + apiKey: 'sk-ckb-prod-a1b2c3d4e5f6g7h8', + apiSecret: 'sec-ckb-x9y8z7w6v5u4t3s2', status: 'active', permissions: { dataIngest: true, @@ -143,16 +161,34 @@ const DEFAULT_PARTNERS: Partner[] = [ tagEnrich: true, batchProcess: true, }, + fieldPermissions: { + phone: true, + qq: true, + wechat: true, + rfm_score: true, + user_level: true, + tags: true, + behavior: true, + location: false, + }, quotaConfig: { dailyLimit: 10000, monthlyLimit: 300000, rateLimit: 100, }, + webhookConfig: { + url: 'https://api.cunkebao.com/webhook/shensheshou', + events: ['data_synced', 'tag_enriched', 'task_completed'], + secret: 'whsec_ckb_xxxx', + enabled: true, + }, statistics: { totalCalls: 158420, todayCalls: 1520, monthCalls: 45800, lastCallTime: '2026-01-31 18:45:22', + successRate: 99.2, + avgResponseTime: 85, }, createdAt: '2025-06-15', updatedAt: '2026-01-31', @@ -162,8 +198,8 @@ const DEFAULT_PARTNERS: Partner[] = [ name: '点了码', type: 'external', description: '线下扫码营销系统,同步用户行为数据', - apiKey: 'sk-dlm-xxxxxxxxxx', - apiSecret: 'sec-dlm-xxxxxxxxxx', + apiKey: 'sk-dlm-prod-h8g7f6e5d4c3b2a1', + apiSecret: 'sec-dlm-s2t3u4v5w6x7y8z9', status: 'active', permissions: { dataIngest: true, @@ -171,6 +207,16 @@ const DEFAULT_PARTNERS: Partner[] = [ tagEnrich: false, batchProcess: false, }, + fieldPermissions: { + phone: true, + qq: false, + wechat: false, + rfm_score: true, + user_level: true, + tags: true, + behavior: false, + location: false, + }, quotaConfig: { dailyLimit: 5000, monthlyLimit: 150000, @@ -181,6 +227,8 @@ const DEFAULT_PARTNERS: Partner[] = [ todayCalls: 820, monthCalls: 28500, lastCallTime: '2026-01-31 18:30:15', + successRate: 98.5, + avgResponseTime: 120, }, createdAt: '2025-08-20', updatedAt: '2026-01-31', @@ -190,8 +238,8 @@ const DEFAULT_PARTNERS: Partner[] = [ name: '飞书机器人', type: 'internal', description: '企业内部飞书机器人,用于用户查询和报告推送', - apiKey: 'sk-fs-xxxxxxxxxx', - apiSecret: 'sec-fs-xxxxxxxxxx', + apiKey: 'sk-fs-internal-i9j0k1l2m3n4o5p6', + apiSecret: 'sec-fs-q7r8s9t0u1v2w3x4', status: 'active', permissions: { dataIngest: false, @@ -199,6 +247,16 @@ const DEFAULT_PARTNERS: Partner[] = [ tagEnrich: false, batchProcess: false, }, + fieldPermissions: { + phone: true, + qq: true, + wechat: true, + rfm_score: true, + user_level: true, + tags: true, + behavior: false, + location: false, + }, quotaConfig: { dailyLimit: 1000, monthlyLimit: 30000, @@ -209,10 +267,58 @@ const DEFAULT_PARTNERS: Partner[] = [ todayCalls: 156, monthCalls: 4200, lastCallTime: '2026-01-31 18:50:00', + successRate: 99.8, + avgResponseTime: 45, }, createdAt: '2025-10-10', updatedAt: '2026-01-31', }, + { + id: 'wecom_001', + name: '企业微信', + type: 'internal', + description: '企业微信客服系统,实时查询客户画像', + apiKey: 'sk-wc-internal-y5z6a7b8c9d0e1f2', + apiSecret: 'sec-wc-g3h4i5j6k7l8m9n0', + status: 'active', + permissions: { + dataIngest: true, + dataQuery: true, + tagEnrich: true, + batchProcess: false, + }, + fieldPermissions: { + phone: true, + qq: false, + wechat: true, + rfm_score: true, + user_level: true, + tags: true, + behavior: true, + location: true, + }, + quotaConfig: { + dailyLimit: 8000, + monthlyLimit: 240000, + rateLimit: 80, + }, + webhookConfig: { + url: 'https://qyapi.weixin.qq.com/callback/shensheshou', + events: ['tag_enriched'], + secret: 'whsec_wc_xxxx', + enabled: true, + }, + statistics: { + totalCalls: 45680, + todayCalls: 680, + monthCalls: 18500, + lastCallTime: '2026-01-31 19:02:15', + successRate: 99.5, + avgResponseTime: 62, + }, + createdAt: '2025-09-01', + updatedAt: '2026-01-31', + }, ] // 开放API端点列表 @@ -495,12 +601,29 @@ export default function OpenAPIPage() { tagEnrich: false, batchProcess: false, }, + fieldPermissions: { + phone: true, + qq: true, + wechat: false, + rfm_score: true, + user_level: true, + tags: true, + behavior: false, + location: false, + }, quotaConfig: { dailyLimit: 5000, monthlyLimit: 150000, rateLimit: 50, }, + webhookUrl: '', }) + + // 显示/隐藏密钥状态 + const [showSecrets, setShowSecrets] = useState>(new Set()) + + // 数据流可视化状态 + const [showDataFlow, setShowDataFlow] = useState(false) useEffect(() => { const host = typeof window !== 'undefined' ? window.location.origin : '' @@ -566,8 +689,8 @@ export default function OpenAPIPage() { const handleAddPartner = () => { const newId = `partner_${Date.now()}` - const apiKey = `sk-${newPartner.type.slice(0, 3)}-${Math.random().toString(36).slice(2, 12)}` - const apiSecret = `sec-${newPartner.type.slice(0, 3)}-${Math.random().toString(36).slice(2, 12)}` + const apiKey = `sk-${newPartner.type.slice(0, 3)}-${Math.random().toString(36).slice(2, 20)}` + const apiSecret = `sec-${newPartner.type.slice(0, 3)}-${Math.random().toString(36).slice(2, 20)}` const partner: Partner = { id: newId, @@ -578,12 +701,21 @@ export default function OpenAPIPage() { apiSecret, status: 'pending', permissions: newPartner.permissions, + fieldPermissions: newPartner.fieldPermissions, quotaConfig: newPartner.quotaConfig, + webhookConfig: newPartner.webhookUrl ? { + url: newPartner.webhookUrl, + events: ['data_synced', 'tag_enriched'], + secret: `whsec_${Math.random().toString(36).slice(2, 12)}`, + enabled: true, + } : undefined, statistics: { totalCalls: 0, todayCalls: 0, monthCalls: 0, lastCallTime: '-', + successRate: 0, + avgResponseTime: 0, }, createdAt: new Date().toISOString().split('T')[0], updatedAt: new Date().toISOString().split('T')[0], @@ -601,14 +733,39 @@ export default function OpenAPIPage() { tagEnrich: false, batchProcess: false, }, + fieldPermissions: { + phone: true, + qq: true, + wechat: false, + rfm_score: true, + user_level: true, + tags: true, + behavior: false, + location: false, + }, quotaConfig: { dailyLimit: 5000, monthlyLimit: 150000, rateLimit: 50, }, + webhookUrl: '', }) } + const toggleSecretVisibility = (id: string) => { + const newSet = new Set(showSecrets) + if (newSet.has(id)) { + newSet.delete(id) + } else { + newSet.add(id) + } + setShowSecrets(newSet) + } + + const maskSecret = (secret: string) => { + return secret.substring(0, 8) + '••••••••••••' + } + const togglePartnerStatus = (partnerId: string) => { setPartners(partners.map(p => { if (p.id === partnerId) { @@ -660,14 +817,17 @@ export default function OpenAPIPage() {
{/* 统计卡片 */} -
+
-

接入方数量

+

接入方

{partners.length}

-

{activePartners} 个已启用

+
+ +

{activePartners} 在线

+
@@ -681,7 +841,13 @@ export default function OpenAPIPage() {

今日调用

{totalCalls.toLocaleString()}

-

总配额 {totalQuota.toLocaleString()}

+
+
+
+

{((totalCalls / totalQuota) * 100).toFixed(1)}% 配额

@@ -694,8 +860,8 @@ export default function OpenAPIPage() {

数据流入

-

1,520

-

+12.5% 较昨日

+

2,156

+

↑ 18.5% 较昨日

@@ -708,8 +874,8 @@ export default function OpenAPIPage() {

标签完善

-

856

-

98.5% 成功率

+

1,280

+

99.2% 成功率

@@ -717,8 +883,107 @@ export default function OpenAPIPage() {
+ + +
+
+

平均响应

+

78ms

+

↓ 12ms 较昨日

+
+
+ +
+
+
+
+ {/* 数据流可视化 */} + + +
+

实时数据流

+
+ + 实时同步中 +
+
+
+ {/* 外部数据源 */} +
+
+ {partners.filter(p => p.status === 'active').slice(0, 4).map(p => ( +
+

{p.name}

+

{p.statistics.todayCalls.toLocaleString()}

+
+ ))} +
+

外部数据源

+
+ + {/* 数据流箭头 */} +
+
+
+
+ +

流入

+
+
+
+
+ + {/* 神射手中台 */} +
+
+ +
+
+

神射手

+

AI标签引擎

+
+
+ + {/* 输出箭头 */} +
+
+
+
+ +

回传

+
+
+
+
+ + {/* 输出结果 */} +
+
+
+

用户画像

+

完善

+
+
+

AI标签

+

增强

+
+
+

RFM评分

+

计算

+
+
+

流量池

+

分组

+
+
+

数据增值输出

+
+
+
+
+ {/* 快速开始指南 */} @@ -770,6 +1035,10 @@ export default function OpenAPIPage() { 接入方管理 + + + 字段授权 + API端点 @@ -926,11 +1195,16 @@ export default function OpenAPIPage() { {partner.type === 'external' ? '外部系统' : '内部系统'} + {partner.webhookConfig?.enabled && ( + Webhook + )}

{partner.description}

创建于 {partner.createdAt} 最后调用 {partner.statistics.lastCallTime} + {partner.statistics.successRate}% 成功率 + {partner.statistics.avgResponseTime}ms 平均响应
@@ -965,8 +1239,8 @@ export default function OpenAPIPage() {
{/* 权限和配额 */} -
- {/* 权限 */} +
+ {/* 接口权限 */}

接口权限

@@ -1000,6 +1274,24 @@ export default function OpenAPIPage() {
+ {/* 字段权限 */} +
+

字段授权

+
+ {Object.entries(partner.fieldPermissions).filter(([_, v]) => v).map(([key]) => ( + + {key === 'phone' ? '手机号' : + key === 'qq' ? 'QQ号' : + key === 'wechat' ? '微信号' : + key === 'rfm_score' ? 'RFM评分' : + key === 'user_level' ? '用户等级' : + key === 'tags' ? '标签' : + key === 'behavior' ? '行为数据' : + key === 'location' ? '位置信息' : key} + + ))} +
+
{/* 统计 */}

调用统计

@@ -1026,6 +1318,110 @@ export default function OpenAPIPage() {
+ {/* 字段授权管理 */} + + + +
+
+ 字段级授权管理 + 精确控制每个接入方可访问的数据字段 +
+
+
+ + + + + 接入方 + 手机号 + QQ号 + 微信号 + RFM评分 + 用户等级 + 标签 + 行为数据 + 位置信息 + + + + {partners.map(partner => ( + + +
+
+ {partner.name} +
+
+ {(['phone', 'qq', 'wechat', 'rfm_score', 'user_level', 'tags', 'behavior', 'location'] as const).map(field => ( + + { + setPartners(partners.map(p => { + if (p.id === partner.id) { + return { + ...p, + fieldPermissions: { + ...p.fieldPermissions, + [field]: !p.fieldPermissions[field], + }, + } + } + return p + })) + }} + className="mx-auto" + /> + + ))} +
+ ))} +
+
+ + {/* 字段说明 */} +
+

字段说明

+
+
+ 手机号 + 用户手机号码(脱敏处理) +
+
+ QQ号 + 用户QQ账号 +
+
+ 微信号 + 用户微信号/openid +
+
+ RFM评分 + 最近/频率/金额综合评分 +
+
+ 用户等级 + S/A/B/C/D五级分类 +
+
+ 标签 + AI分析生成的用户标签 +
+
+ 行为数据 + 用户行为轨迹和偏好 +
+
+ 位置信息 + 用户地理位置(敏感) +
+
+
+
+
+
+ {/* API端点 */} {/* 分类筛选 */} @@ -1187,58 +1583,65 @@ export default function OpenAPIPage() { {/* 添加接入方弹窗 */} - + 添加接入方 - 配置第三方系统接入神射手数据中台 + 配置第三方系统接入神射手数据中台,创建后将自动生成API密钥 -
-
- - setNewPartner({ ...newPartner, name: e.target.value })} - /> -
-
- - +
+ {/* 基本信息 */} +
+
+ + setNewPartner({ ...newPartner, name: e.target.value })} + /> +
+
+ + +
+