mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: add comprehensive Makefile for project management (#1)
* fix: 修复timeRange=7days时的数据加载和显示问题 ## 修复内容 ### 后端修复 (src/routes/admin.js) - 添加 /admin/usage-costs 接口对 period=7days 的支持 - 实现7天时间范围的成本统计,汇总最近7天的daily数据 - 修复时区处理不一致导致的数据过滤错误 ### 前端修复 (web/admin-spa/src/stores/dashboard.js) - 修改 loadDashboardData() 支持动态 timeRange 参数 - 根据时间范围动态调整 usage-costs 查询参数 - 消除硬编码的 period=today 和 period=all ### 前端修复 (web/admin-spa/src/views/ApiKeysView.vue) - 修正API Key详情统计的period计算逻辑 - 7days时间范围现在正确传递 period=daily 而非 monthly - 确保列表数据和详情统计使用一致的时间范围 ## 解决的问题 - 选择"最近7天"时数据显示不准确或缺失 - API Key详情展开时period参数错误 - 成本统计不跟随时间范围选择变化 - 时区计算不一致导致的边界问题 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: add comprehensive Makefile for project management - Add common development commands (install, setup, dev, start) - Add frontend build commands (build-web, build-all) - Add service management with daemon support - Add Docker deployment commands - Add CLI management tools shortcuts - Add maintenance and monitoring commands - Include Chinese descriptions for better UX 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
259
Makefile
Normal file
259
Makefile
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
# Claude Relay Service Makefile
|
||||||
|
# 功能完整的 AI API 中转服务,支持 Claude 和 Gemini 双平台
|
||||||
|
|
||||||
|
.PHONY: help install setup dev start test lint clean docker-up docker-down service-start service-stop service-status logs cli-admin cli-keys cli-accounts cli-status
|
||||||
|
|
||||||
|
# 默认目标:显示帮助信息
|
||||||
|
help:
|
||||||
|
@echo "Claude Relay Service - AI API 中转服务"
|
||||||
|
@echo ""
|
||||||
|
@echo "可用命令:"
|
||||||
|
@echo ""
|
||||||
|
@echo " 📦 安装和初始化:"
|
||||||
|
@echo " install - 安装项目依赖"
|
||||||
|
@echo " install-web - 安装Web界面依赖"
|
||||||
|
@echo " setup - 生成配置文件和管理员凭据"
|
||||||
|
@echo " clean - 清理依赖和构建文件"
|
||||||
|
@echo ""
|
||||||
|
@echo " 🎨 前端构建:"
|
||||||
|
@echo " build-web - 构建 Web 管理界面"
|
||||||
|
@echo " build-all - 构建完整项目(后端+前端)"
|
||||||
|
@echo ""
|
||||||
|
@echo " 🚀 开发和运行:"
|
||||||
|
@echo " dev - 开发模式运行(热重载)"
|
||||||
|
@echo " start - 生产模式运行"
|
||||||
|
@echo " test - 运行测试套件"
|
||||||
|
@echo " lint - 代码风格检查"
|
||||||
|
@echo ""
|
||||||
|
@echo " 🐳 Docker 部署:"
|
||||||
|
@echo " docker-up - 启动 Docker 服务"
|
||||||
|
@echo " docker-up-full - 启动 Docker 服务(包含监控)"
|
||||||
|
@echo " docker-down - 停止 Docker 服务"
|
||||||
|
@echo " docker-logs - 查看 Docker 日志"
|
||||||
|
@echo ""
|
||||||
|
@echo " 🔧 服务管理:"
|
||||||
|
@echo " service-start - 前台启动服务"
|
||||||
|
@echo " service-daemon - 后台启动服务(守护进程)"
|
||||||
|
@echo " service-stop - 停止服务"
|
||||||
|
@echo " service-restart - 重启服务"
|
||||||
|
@echo " service-restart-daemon - 重启服务(守护进程)"
|
||||||
|
@echo " service-status - 查看服务状态"
|
||||||
|
@echo " logs - 查看应用日志"
|
||||||
|
@echo " logs-follow - 实时查看日志"
|
||||||
|
@echo ""
|
||||||
|
@echo " ⚙️ CLI 管理工具:"
|
||||||
|
@echo " cli-admin - 管理员操作"
|
||||||
|
@echo " cli-keys - API Key 管理"
|
||||||
|
@echo " cli-accounts - Claude 账户管理"
|
||||||
|
@echo " cli-status - 系统状态查看"
|
||||||
|
@echo ""
|
||||||
|
@echo " 💡 快速开始:"
|
||||||
|
@echo " make setup && make dev"
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
# 安装和初始化
|
||||||
|
install:
|
||||||
|
@echo "📦 安装项目依赖..."
|
||||||
|
npm install
|
||||||
|
|
||||||
|
install-web:
|
||||||
|
@echo "📦 安装 Web 界面依赖..."
|
||||||
|
npm run install:web
|
||||||
|
|
||||||
|
# 前端构建
|
||||||
|
build-web:
|
||||||
|
@echo "🎨 构建 Web 管理界面..."
|
||||||
|
npm run build:web
|
||||||
|
|
||||||
|
build-all: install install-web build-web
|
||||||
|
@echo "🎉 完整项目构建完成!"
|
||||||
|
|
||||||
|
setup:
|
||||||
|
@echo "⚙️ 初始化项目配置和管理员凭据..."
|
||||||
|
@if [ ! -f config/config.js ]; then cp config/config.example.js config/config.js; fi
|
||||||
|
@if [ ! -f .env ]; then cp .env.example .env; fi
|
||||||
|
npm run setup
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo "🧹 清理依赖和构建文件..."
|
||||||
|
rm -rf node_modules
|
||||||
|
rm -rf web/node_modules
|
||||||
|
rm -rf web/admin-spa/dist
|
||||||
|
rm -rf web/admin-spa/node_modules
|
||||||
|
rm -rf logs/*.log
|
||||||
|
|
||||||
|
# 开发和运行
|
||||||
|
dev:
|
||||||
|
@echo "🚀 启动开发模式(热重载)..."
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
start:
|
||||||
|
@echo "🚀 启动生产模式..."
|
||||||
|
npm start
|
||||||
|
|
||||||
|
test:
|
||||||
|
@echo "🧪 运行测试套件..."
|
||||||
|
npm test
|
||||||
|
|
||||||
|
lint:
|
||||||
|
@echo "🔍 执行代码风格检查..."
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# Docker 部署
|
||||||
|
docker-up:
|
||||||
|
@echo "🐳 启动 Docker 服务..."
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
docker-up-full:
|
||||||
|
@echo "🐳 启动 Docker 服务(包含监控)..."
|
||||||
|
docker-compose --profile monitoring up -d
|
||||||
|
|
||||||
|
docker-down:
|
||||||
|
@echo "🛑 停止 Docker 服务..."
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
docker-logs:
|
||||||
|
@echo "📋 查看 Docker 服务日志..."
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# 服务管理
|
||||||
|
service-start:
|
||||||
|
@echo "🚀 前台启动服务..."
|
||||||
|
npm run service:start
|
||||||
|
|
||||||
|
service-daemon:
|
||||||
|
@echo "🔧 后台启动服务(守护进程)..."
|
||||||
|
npm run service:start:daemon
|
||||||
|
|
||||||
|
service-stop:
|
||||||
|
@echo "🛑 停止服务..."
|
||||||
|
npm run service:stop
|
||||||
|
|
||||||
|
service-restart:
|
||||||
|
@echo "🔄 重启服务..."
|
||||||
|
npm run service:restart
|
||||||
|
|
||||||
|
service-restart-daemon:
|
||||||
|
@echo "🔄 重启服务(守护进程)..."
|
||||||
|
npm run service:restart:daemon
|
||||||
|
|
||||||
|
service-status:
|
||||||
|
@echo "📊 查看服务状态..."
|
||||||
|
npm run service:status
|
||||||
|
|
||||||
|
logs:
|
||||||
|
@echo "📋 查看应用日志..."
|
||||||
|
npm run service:logs
|
||||||
|
|
||||||
|
logs-follow:
|
||||||
|
@echo "📋 实时查看日志..."
|
||||||
|
npm run service:logs:follow
|
||||||
|
|
||||||
|
# CLI 管理工具
|
||||||
|
cli-admin:
|
||||||
|
@echo "👤 启动管理员操作 CLI..."
|
||||||
|
npm run cli admin
|
||||||
|
|
||||||
|
cli-keys:
|
||||||
|
@echo "🔑 启动 API Key 管理 CLI..."
|
||||||
|
npm run cli keys
|
||||||
|
|
||||||
|
cli-accounts:
|
||||||
|
@echo "👥 启动 Claude 账户管理 CLI..."
|
||||||
|
npm run cli accounts
|
||||||
|
|
||||||
|
cli-status:
|
||||||
|
@echo "📊 查看系统状态..."
|
||||||
|
npm run cli status
|
||||||
|
|
||||||
|
# 开发辅助命令
|
||||||
|
check-config:
|
||||||
|
@echo "🔍 检查配置文件..."
|
||||||
|
@if [ ! -f config/config.js ]; then echo "❌ config/config.js 不存在,请运行 'make setup'"; exit 1; fi
|
||||||
|
@if [ ! -f .env ]; then echo "❌ .env 不存在,请运行 'make setup'"; exit 1; fi
|
||||||
|
@echo "✅ 配置文件检查通过"
|
||||||
|
|
||||||
|
health-check:
|
||||||
|
@echo "🏥 执行健康检查..."
|
||||||
|
@curl -s http://localhost:3000/health || echo "❌ 服务未运行或不可访问"
|
||||||
|
|
||||||
|
# 快速启动组合命令
|
||||||
|
quick-start: setup dev
|
||||||
|
|
||||||
|
quick-daemon: setup service-daemon
|
||||||
|
@echo "🎉 服务已在后台启动!"
|
||||||
|
@echo "运行 'make service-status' 查看状态"
|
||||||
|
@echo "运行 'make logs-follow' 查看实时日志"
|
||||||
|
|
||||||
|
# 全栈开发环境
|
||||||
|
dev-full: install install-web build-web setup dev
|
||||||
|
@echo "🚀 全栈开发环境启动!"
|
||||||
|
|
||||||
|
# 完整部署流程
|
||||||
|
deploy: clean install install-web build-web setup test lint docker-up
|
||||||
|
@echo "🎉 部署完成!"
|
||||||
|
@echo "访问 Web 管理界面: http://localhost:3000/web"
|
||||||
|
@echo "API 端点: http://localhost:3000/api/v1/messages"
|
||||||
|
|
||||||
|
# 生产部署准备
|
||||||
|
production-build: clean install install-web build-web
|
||||||
|
@echo "🚀 生产环境构建完成!"
|
||||||
|
|
||||||
|
# 维护命令
|
||||||
|
backup-redis:
|
||||||
|
@echo "💾 备份 Redis 数据..."
|
||||||
|
@docker exec claude-relay-service-redis-1 redis-cli BGSAVE || echo "❌ Redis 备份失败"
|
||||||
|
|
||||||
|
restore-redis:
|
||||||
|
@echo "♻️ 恢复 Redis 数据..."
|
||||||
|
@echo "请手动恢复 Redis 数据文件"
|
||||||
|
|
||||||
|
# 监控和日志
|
||||||
|
monitor:
|
||||||
|
@echo "📊 启动监控面板..."
|
||||||
|
@echo "Grafana: http://localhost:3001"
|
||||||
|
@echo "Redis Commander: http://localhost:8081"
|
||||||
|
|
||||||
|
tail-logs:
|
||||||
|
@echo "📋 实时查看日志..."
|
||||||
|
tail -f logs/claude-relay-*.log
|
||||||
|
|
||||||
|
# 开发工具
|
||||||
|
format:
|
||||||
|
@echo "🎨 格式化代码..."
|
||||||
|
npm run lint -- --fix
|
||||||
|
|
||||||
|
check-deps:
|
||||||
|
@echo "🔍 检查依赖更新..."
|
||||||
|
npm outdated
|
||||||
|
|
||||||
|
update-deps:
|
||||||
|
@echo "⬆️ 更新依赖..."
|
||||||
|
npm update
|
||||||
|
|
||||||
|
# 测试相关
|
||||||
|
test-coverage:
|
||||||
|
@echo "📊 运行测试覆盖率..."
|
||||||
|
npm test -- --coverage
|
||||||
|
|
||||||
|
test-watch:
|
||||||
|
@echo "👀 监视模式运行测试..."
|
||||||
|
npm test -- --watch
|
||||||
|
|
||||||
|
# Git 相关
|
||||||
|
git-status:
|
||||||
|
@echo "📋 Git 状态..."
|
||||||
|
git status --short
|
||||||
|
|
||||||
|
git-pull:
|
||||||
|
@echo "⬇️ 拉取最新代码..."
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
# 安全检查
|
||||||
|
security-audit:
|
||||||
|
@echo "🔒 执行安全审计..."
|
||||||
|
npm audit
|
||||||
|
|
||||||
|
security-fix:
|
||||||
|
@echo "🔧 修复安全漏洞..."
|
||||||
|
npm audit fix
|
||||||
@@ -2724,7 +2724,7 @@ router.get('/api-keys-usage-trend', authenticateAdmin, async (req, res) => {
|
|||||||
// 计算总体使用费用
|
// 计算总体使用费用
|
||||||
router.get('/usage-costs', authenticateAdmin, async (req, res) => {
|
router.get('/usage-costs', authenticateAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { period = 'all' } = req.query; // all, today, monthly
|
const { period = 'all' } = req.query; // all, today, monthly, 7days
|
||||||
|
|
||||||
logger.info(`💰 Calculating usage costs for period: ${period}`);
|
logger.info(`💰 Calculating usage costs for period: ${period}`);
|
||||||
|
|
||||||
@@ -2752,6 +2752,95 @@ router.get('/usage-costs', authenticateAdmin, async (req, res) => {
|
|||||||
pattern = `usage:model:daily:*:${today}`;
|
pattern = `usage:model:daily:*:${today}`;
|
||||||
} else if (period === 'monthly') {
|
} else if (period === 'monthly') {
|
||||||
pattern = `usage:model:monthly:*:${currentMonth}`;
|
pattern = `usage:model:monthly:*:${currentMonth}`;
|
||||||
|
} else if (period === '7days') {
|
||||||
|
// 最近7天:汇总daily数据
|
||||||
|
const modelUsageMap = new Map();
|
||||||
|
|
||||||
|
// 获取最近7天的所有daily统计数据
|
||||||
|
for (let i = 0; i < 7; i++) {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() - i);
|
||||||
|
const tzDate = redis.getDateInTimezone(date);
|
||||||
|
const dateStr = `${tzDate.getUTCFullYear()}-${String(tzDate.getUTCMonth() + 1).padStart(2, '0')}-${String(tzDate.getUTCDate()).padStart(2, '0')}`;
|
||||||
|
const dayPattern = `usage:model:daily:*:${dateStr}`;
|
||||||
|
|
||||||
|
const dayKeys = await client.keys(dayPattern);
|
||||||
|
|
||||||
|
for (const key of dayKeys) {
|
||||||
|
const modelMatch = key.match(/usage:model:daily:(.+):\d{4}-\d{2}-\d{2}$/);
|
||||||
|
if (!modelMatch) continue;
|
||||||
|
|
||||||
|
const model = modelMatch[1];
|
||||||
|
const data = await client.hgetall(key);
|
||||||
|
|
||||||
|
if (data && Object.keys(data).length > 0) {
|
||||||
|
if (!modelUsageMap.has(model)) {
|
||||||
|
modelUsageMap.set(model, {
|
||||||
|
inputTokens: 0,
|
||||||
|
outputTokens: 0,
|
||||||
|
cacheCreateTokens: 0,
|
||||||
|
cacheReadTokens: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelUsage = modelUsageMap.get(model);
|
||||||
|
modelUsage.inputTokens += parseInt(data.inputTokens) || 0;
|
||||||
|
modelUsage.outputTokens += parseInt(data.outputTokens) || 0;
|
||||||
|
modelUsage.cacheCreateTokens += parseInt(data.cacheCreateTokens) || 0;
|
||||||
|
modelUsage.cacheReadTokens += parseInt(data.cacheReadTokens) || 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算7天统计的费用
|
||||||
|
logger.info(`💰 Processing ${modelUsageMap.size} unique models for 7days cost calculation`);
|
||||||
|
|
||||||
|
for (const [model, usage] of modelUsageMap) {
|
||||||
|
const usageData = {
|
||||||
|
input_tokens: usage.inputTokens,
|
||||||
|
output_tokens: usage.outputTokens,
|
||||||
|
cache_creation_input_tokens: usage.cacheCreateTokens,
|
||||||
|
cache_read_input_tokens: usage.cacheReadTokens
|
||||||
|
};
|
||||||
|
|
||||||
|
const costResult = CostCalculator.calculateCost(usageData, model);
|
||||||
|
totalCosts.inputCost += costResult.costs.input;
|
||||||
|
totalCosts.outputCost += costResult.costs.output;
|
||||||
|
totalCosts.cacheCreateCost += costResult.costs.cacheWrite;
|
||||||
|
totalCosts.cacheReadCost += costResult.costs.cacheRead;
|
||||||
|
totalCosts.totalCost += costResult.costs.total;
|
||||||
|
|
||||||
|
logger.info(`💰 Model ${model} (7days): ${usage.inputTokens + usage.outputTokens + usage.cacheCreateTokens + usage.cacheReadTokens} tokens, cost: ${costResult.formatted.total}`);
|
||||||
|
|
||||||
|
// 记录模型费用
|
||||||
|
modelCosts[model] = {
|
||||||
|
model,
|
||||||
|
requests: 0, // 7天汇总数据没有请求数统计
|
||||||
|
usage: usageData,
|
||||||
|
costs: costResult.costs,
|
||||||
|
formatted: costResult.formatted,
|
||||||
|
usingDynamicPricing: costResult.usingDynamicPricing
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回7天统计结果
|
||||||
|
return res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
period,
|
||||||
|
totalCosts: {
|
||||||
|
...totalCosts,
|
||||||
|
formatted: {
|
||||||
|
inputCost: CostCalculator.formatCost(totalCosts.inputCost),
|
||||||
|
outputCost: CostCalculator.formatCost(totalCosts.outputCost),
|
||||||
|
cacheCreateCost: CostCalculator.formatCost(totalCosts.cacheCreateCost),
|
||||||
|
cacheReadCost: CostCalculator.formatCost(totalCosts.cacheReadCost),
|
||||||
|
totalCost: CostCalculator.formatCost(totalCosts.totalCost)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modelCosts: Object.values(modelCosts)
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// 全部时间,先尝试从Redis获取所有历史模型统计数据(只使用monthly数据避免重复计算)
|
// 全部时间,先尝试从Redis获取所有历史模型统计数据(只使用monthly数据避免重复计算)
|
||||||
const allModelKeys = await client.keys('usage:model:monthly:*:*');
|
const allModelKeys = await client.keys('usage:model:monthly:*:*');
|
||||||
|
|||||||
@@ -120,13 +120,26 @@ export const useDashboardStore = defineStore('dashboard', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
async function loadDashboardData() {
|
async function loadDashboardData(timeRange = null) {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
|
// 根据timeRange动态设置costs查询参数
|
||||||
|
let costsParams = { today: 'today', all: 'all' }
|
||||||
|
|
||||||
|
if (timeRange) {
|
||||||
|
const periodMapping = {
|
||||||
|
'today': { today: 'today', all: 'today' },
|
||||||
|
'7days': { today: '7days', all: '7days' },
|
||||||
|
'monthly': { today: 'monthly', all: 'monthly' },
|
||||||
|
'all': { today: 'today', all: 'all' }
|
||||||
|
}
|
||||||
|
costsParams = periodMapping[timeRange] || costsParams
|
||||||
|
}
|
||||||
|
|
||||||
const [dashboardResponse, todayCostsResponse, totalCostsResponse] = await Promise.all([
|
const [dashboardResponse, todayCostsResponse, totalCostsResponse] = await Promise.all([
|
||||||
apiClient.get('/admin/dashboard'),
|
apiClient.get('/admin/dashboard'),
|
||||||
apiClient.get('/admin/usage-costs?period=today'),
|
apiClient.get(`/admin/usage-costs?period=${costsParams.today}`),
|
||||||
apiClient.get('/admin/usage-costs?period=all')
|
apiClient.get(`/admin/usage-costs?period=${costsParams.all}`)
|
||||||
])
|
])
|
||||||
|
|
||||||
if (dashboardResponse.success) {
|
if (dashboardResponse.success) {
|
||||||
|
|||||||
@@ -1306,7 +1306,7 @@ const loadApiKeyModelStats = async (keyId, forceReload = false) => {
|
|||||||
params.append('endDate', filter.customEnd)
|
params.append('endDate', filter.customEnd)
|
||||||
params.append('period', 'custom')
|
params.append('period', 'custom')
|
||||||
} else {
|
} else {
|
||||||
const period = filter.preset === 'today' ? 'daily' : 'monthly'
|
const period = filter.preset === 'today' ? 'daily' : filter.preset === '7days' ? 'daily' : 'monthly'
|
||||||
params.append('period', period)
|
params.append('period', period)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user