Spaces:
Paused
Paused
Upload 4 files
Browse files- docker-compose.yml +31 -0
- index.js +97 -0
- package-lock.json +0 -0
- package.json +28 -0
docker-compose.yml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
services:
|
| 2 |
+
# MongoDB 服务
|
| 3 |
+
mongodb:
|
| 4 |
+
image: mongo:8.0
|
| 5 |
+
restart: unless-stopped
|
| 6 |
+
expose:
|
| 7 |
+
- "27017"
|
| 8 |
+
environment:
|
| 9 |
+
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME:-admin}
|
| 10 |
+
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD:-password}
|
| 11 |
+
volumes:
|
| 12 |
+
- ./data:/data/db
|
| 13 |
+
healthcheck:
|
| 14 |
+
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
|
| 15 |
+
interval: 30s
|
| 16 |
+
timeout: 10s
|
| 17 |
+
retries: 3
|
| 18 |
+
start_period: 40s
|
| 19 |
+
|
| 20 |
+
app:
|
| 21 |
+
image: ghcr.io/1812z/runtime_tracker:latest
|
| 22 |
+
restart: unless-stopped
|
| 23 |
+
ports:
|
| 24 |
+
- "${PORT:-3000}:${PORT:-3000}"
|
| 25 |
+
environment:
|
| 26 |
+
- MONGODB_URI=${MONGODB_URI:-mongodb://admin:password@mongodb:27017/deviceStats?authSource=admin}
|
| 27 |
+
volumes:
|
| 28 |
+
- ./.env:/app/.env:rw
|
| 29 |
+
depends_on:
|
| 30 |
+
mongodb:
|
| 31 |
+
condition: service_healthy
|
index.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
require('dotenv').config(); // 放在文件最开头
|
| 2 |
+
const express = require('express');
|
| 3 |
+
const bodyParser = require('body-parser');
|
| 4 |
+
const path = require('path');
|
| 5 |
+
const mongoose = require('mongoose');
|
| 6 |
+
const cors = require('cors');
|
| 7 |
+
|
| 8 |
+
const app = express();
|
| 9 |
+
const HOST = process.env.HOST || '0.0.0.0';
|
| 10 |
+
const PORT = process.env.PORT || 3000;
|
| 11 |
+
const SECRET = process.env.SECRET || 'default-secret-key';
|
| 12 |
+
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/deviceStats';
|
| 13 |
+
const ADMIN_USER = process.env.ADMIN_USER || 'admin';
|
| 14 |
+
const ADMIN_PASSWD = process.env.ADMIN_PASSWD?.replace(/\$\$/g, '$') || '';
|
| 15 |
+
|
| 16 |
+
//运行信息
|
| 17 |
+
console.log('后端端口 ', PORT);
|
| 18 |
+
console.log('后端密钥 ', SECRET);
|
| 19 |
+
console.log('后端MongoDB ', MONGODB_URI);
|
| 20 |
+
|
| 21 |
+
// 中间件
|
| 22 |
+
app.use(cors());
|
| 23 |
+
app.use(bodyParser.json());
|
| 24 |
+
app.use(express.static(path.join(__dirname, 'public')));
|
| 25 |
+
|
| 26 |
+
// MongoDB连接
|
| 27 |
+
mongoose.connect(MONGODB_URI)
|
| 28 |
+
.then(() => console.log('成功连接到 MongoDB'))
|
| 29 |
+
.catch(err => console.error('MongoDB 连接错误:', err));
|
| 30 |
+
|
| 31 |
+
module.exports.mongoose = mongoose;
|
| 32 |
+
module.exports.SECRET = SECRET;
|
| 33 |
+
module.exports.ADMIN_USER = ADMIN_USER;
|
| 34 |
+
module.exports.ADMIN_PASSWD = ADMIN_PASSWD;
|
| 35 |
+
|
| 36 |
+
// 导入模块
|
| 37 |
+
const StatsRecorder = require('./services/StatsRecorder');
|
| 38 |
+
const StatsQuery = require('./services/StatsQuery');
|
| 39 |
+
const AISummary = require('./services/AISummary');
|
| 40 |
+
|
| 41 |
+
// 创建实例
|
| 42 |
+
const statsRecorder = new StatsRecorder(parseInt(process.env.DEFAULT_TIMEZONE_OFFSET) || 8);
|
| 43 |
+
const statsQuery = new StatsQuery(statsRecorder,{
|
| 44 |
+
timezoneOffset: parseInt(process.env.DEFAULT_TIMEZONE_OFFSET) || 8
|
| 45 |
+
});
|
| 46 |
+
|
| 47 |
+
// 创建AI总结实例并配置
|
| 48 |
+
const aiSummary = new AISummary(statsRecorder, statsQuery, {
|
| 49 |
+
// AI API配置 (从环境变量读取)
|
| 50 |
+
aiApiUrl: process.env.AI_API_URL,
|
| 51 |
+
aiApiKey: process.env.AI_API_KEY,
|
| 52 |
+
aiModel: process.env.AI_MODEL || 'gpt-4',
|
| 53 |
+
aiMaxTokens: parseInt(process.env.AI_MAX_TOKENS) || 1000,
|
| 54 |
+
|
| 55 |
+
// 发布API配置
|
| 56 |
+
publishApiUrl: process.env.PUBLISH_API_URL,
|
| 57 |
+
publishApiKey: process.env.PUBLISH_API_KEY,
|
| 58 |
+
|
| 59 |
+
// 默认时区 (东八区)
|
| 60 |
+
timezoneOffset: parseInt(process.env.DEFAULT_TIMEZONE_OFFSET) || 8,
|
| 61 |
+
|
| 62 |
+
// 是否启用定时任务
|
| 63 |
+
enabled: process.env.AI_SUMMARY_ENABLED !== 'false',
|
| 64 |
+
intervalHours: parseInt(process.env.SCHEDULE_INTERVAL_HOURS) || 4,
|
| 65 |
+
|
| 66 |
+
// 发布功能
|
| 67 |
+
publishEnabled: process.env.PUBLISH_ENABLED !== 'false',
|
| 68 |
+
|
| 69 |
+
aiPrompt: process.env.AI_PROMPT?.replace(/\\n/g, '\n')
|
| 70 |
+
});
|
| 71 |
+
|
| 72 |
+
// 导出实例供 apiRoutes 使用
|
| 73 |
+
module.exports.statsRecorder = statsRecorder;
|
| 74 |
+
module.exports.statsQuery = statsQuery;
|
| 75 |
+
module.exports.aiSummary = aiSummary;
|
| 76 |
+
|
| 77 |
+
// 启动AI定时任务
|
| 78 |
+
aiSummary.start();
|
| 79 |
+
|
| 80 |
+
// API路由
|
| 81 |
+
const apiRoutes = require('./routes/apiRoutes');
|
| 82 |
+
const adminRoutes = require('./routes/adminRoutes');
|
| 83 |
+
const eyeapiRoutes = require('./routes/EyeTime_Routes');
|
| 84 |
+
app.use('/admin', adminRoutes);
|
| 85 |
+
app.use('/api', apiRoutes);
|
| 86 |
+
app.use('/api', eyeapiRoutes);
|
| 87 |
+
|
| 88 |
+
// 启动服务器
|
| 89 |
+
app.listen(PORT, HOST, () => {
|
| 90 |
+
console.log(`Server running on http://${HOST}:${PORT}`);
|
| 91 |
+
console.log('[AI Summary]:', aiSummary.enabled ? '已启用' : '已禁用');
|
| 92 |
+
if (aiSummary.enabled && aiSummary.aiConfig.apiKey) {
|
| 93 |
+
console.log('[AI Summary]定时任务: 已启用');
|
| 94 |
+
} else if (aiSummary.enabled && !aiSummary.aiConfig.apiKey) {
|
| 95 |
+
console.log('[AI Summary]警告: AI_API_KEY 未配置,AI功能无法正常工作');
|
| 96 |
+
}
|
| 97 |
+
});
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "runtime_tracker",
|
| 3 |
+
"version": "1.0.0",
|
| 4 |
+
"main": "index.js",
|
| 5 |
+
"scripts": {
|
| 6 |
+
"test": "node index.js",
|
| 7 |
+
"pm2": "pm2 start index.js --name \"runtime_tracker\"",
|
| 8 |
+
"pm2-no-daemon": "pm2 start index.js --name \"runtime_tracker\" --no-daemon"
|
| 9 |
+
},
|
| 10 |
+
"private": true,
|
| 11 |
+
"keywords": [],
|
| 12 |
+
"author": "",
|
| 13 |
+
"license": "ISC",
|
| 14 |
+
"description": "",
|
| 15 |
+
"dependencies": {
|
| 16 |
+
"@vitejs/plugin-vue": "^6.0.1",
|
| 17 |
+
"bcryptjs": "^3.0.2",
|
| 18 |
+
"body-parser": "2.2.1",
|
| 19 |
+
"cors": "^2.8.5",
|
| 20 |
+
"dayjs": "^1.11.19",
|
| 21 |
+
"dotenv": "^17.2.2",
|
| 22 |
+
"express": "^5.1.0",
|
| 23 |
+
"jsonwebtoken": "^9.0.2",
|
| 24 |
+
"mongoose": "^8.18.1",
|
| 25 |
+
"node-cron": "^4.2.1",
|
| 26 |
+
"pm2": "^6.0.13"
|
| 27 |
+
}
|
| 28 |
+
}
|