feat: external API for Java server integration with dedicated key
This commit is contained in:
79
backend/routes/external.js
Normal file
79
backend/routes/external.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const { query, getRow } = require('../db');
|
||||||
|
const { authenticate } = require('../middleware/auth');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const { getConfig } = require('../db');
|
||||||
|
|
||||||
|
function getExternalKey() {
|
||||||
|
try { return getConfig()?.external_api_key || process.env.EXTERNAL_API_KEY || ''; } catch { return process.env.EXTERNAL_API_KEY || ''; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function externalAuth(req, res, next) {
|
||||||
|
const required = getExternalKey();
|
||||||
|
if (!required) return next();
|
||||||
|
if (req.headers['x-external-key'] === required) return next();
|
||||||
|
return res.status(401).json({ error: '未授权' });
|
||||||
|
}
|
||||||
|
|
||||||
|
router.use(externalAuth);
|
||||||
|
|
||||||
|
router.get('/tickets', async (req, res) => {
|
||||||
|
const { type, status, page, limit } = req.query;
|
||||||
|
let q = `SELECT t.id, t.type, t.title, t.status, t.priority,
|
||||||
|
t.reporter_game_name, t.reporter_game_uid,
|
||||||
|
t.target_game_name, t.target_game_uid,
|
||||||
|
t.reason, t.description, t.assigned_to, t.claim_note,
|
||||||
|
t.created_at, t.updated_at
|
||||||
|
FROM tickets t WHERE 1=1`;
|
||||||
|
const p = [];
|
||||||
|
if (type) { q += ' AND t.type = ?'; p.push(type); }
|
||||||
|
if (status) { q += ' AND t.status = ?'; p.push(status); }
|
||||||
|
const pg = parseInt(page) || 1;
|
||||||
|
const lm = Math.min(parseInt(limit) || 50, 200);
|
||||||
|
q += ` ORDER BY t.updated_at DESC LIMIT ${(pg-1)*lm}, ${lm}`;
|
||||||
|
res.json(await query(q, p));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/tickets/:id', async (req, res) => {
|
||||||
|
const t = await getRow(`SELECT id, type, title, status, priority,
|
||||||
|
reporter_game_name, reporter_game_uid, target_game_name, target_game_uid,
|
||||||
|
reason, description, assigned_to, claim_note,
|
||||||
|
created_at, updated_at FROM tickets WHERE id = ?`, [req.params.id]);
|
||||||
|
if (!t) return res.status(404).json({ error: '工单不存在' });
|
||||||
|
t.responses = await query('SELECT content, is_staff, created_at FROM responses WHERE ticket_id = ? ORDER BY created_at ASC', [req.params.id]);
|
||||||
|
t.attachments = await query('SELECT id, original_name, mime_type, size FROM attachments WHERE ticket_id = ?', [req.params.id]);
|
||||||
|
res.json(t);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/reported-players', async (req, res) => {
|
||||||
|
const rows = await query(`SELECT target_game_name, target_game_uid,
|
||||||
|
COUNT(*) as total, SUM(CASE WHEN status IN ('pending','processing','awaiting_info','appealing') THEN 1 ELSE 0 END) as active,
|
||||||
|
SUM(CASE WHEN status='resolved' THEN 1 ELSE 0 END) as resolved,
|
||||||
|
MAX(created_at) as last_report
|
||||||
|
FROM tickets WHERE type='report' AND target_game_name IS NOT NULL AND target_game_name != ''
|
||||||
|
GROUP BY target_game_name, target_game_uid ORDER BY total DESC LIMIT 500`);
|
||||||
|
res.json(rows);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/stats', async (req, res) => {
|
||||||
|
const [[{c:total}], byType, byStatus] = await Promise.all([
|
||||||
|
query('SELECT COUNT(*) as c FROM tickets'),
|
||||||
|
query('SELECT type, COUNT(*) as c FROM tickets GROUP BY type'),
|
||||||
|
query('SELECT status, COUNT(*) as c FROM tickets GROUP BY status'),
|
||||||
|
]);
|
||||||
|
res.json({ total, byType, byStatus });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/users', async (req, res) => {
|
||||||
|
res.json(await query('SELECT id, username, email, game_name, game_uid, role, active, created_at FROM users ORDER BY id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/user/:id', async (req, res) => {
|
||||||
|
const u = await getRow('SELECT id, username, email, game_name, game_uid, role, active, created_at FROM users WHERE id = ?', [req.params.id]);
|
||||||
|
if (!u) return res.status(404).json({ error: '用户不存在' });
|
||||||
|
res.json(u);
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@@ -69,6 +69,7 @@ router.post('/complete', async (req, res) => {
|
|||||||
|
|
||||||
const jwtSecret = crypto.randomBytes(32).toString('hex');
|
const jwtSecret = crypto.randomBytes(32).toString('hex');
|
||||||
const apiKey = crypto.randomBytes(24).toString('hex');
|
const apiKey = crypto.randomBytes(24).toString('hex');
|
||||||
|
const externalKey = crypto.randomBytes(24).toString('hex');
|
||||||
|
|
||||||
for (const [k, v, l] of [
|
for (const [k, v, l] of [
|
||||||
['site_name', site_name || 'MC举报系统', '站点名称'],
|
['site_name', site_name || 'MC举报系统', '站点名称'],
|
||||||
@@ -86,6 +87,7 @@ router.post('/complete', async (req, res) => {
|
|||||||
db_user, db_pass: db_pass || '', db_name,
|
db_user, db_pass: db_pass || '', db_name,
|
||||||
jwt_secret: jwtSecret,
|
jwt_secret: jwtSecret,
|
||||||
api_key: apiKey,
|
api_key: apiKey,
|
||||||
|
external_api_key: externalKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({ ok: true, message: '安装完成,请重新启动服务器' });
|
res.json({ ok: true, message: '安装完成,请重新启动服务器' });
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ if (isInstalled()) {
|
|||||||
app.use('/api/export', methodGuard(['GET']), generalLimiter, require('./routes/export'));
|
app.use('/api/export', methodGuard(['GET']), generalLimiter, require('./routes/export'));
|
||||||
app.use('/api/uploads', methodGuard(['GET']), generalLimiter, require('./routes/uploads'));
|
app.use('/api/uploads', methodGuard(['GET']), generalLimiter, require('./routes/uploads'));
|
||||||
app.use('/api/notifications', methodGuard(['GET','POST','PUT','DELETE']), generalLimiter, require('./routes/notifications'));
|
app.use('/api/notifications', methodGuard(['GET','POST','PUT','DELETE']), generalLimiter, require('./routes/notifications'));
|
||||||
|
app.use('/api/external', require('./routes/external'));
|
||||||
|
|
||||||
app.get('/api/verify', (req, res) => res.redirect(`/#/verify?token=${req.query.token}`));
|
app.get('/api/verify', (req, res) => res.redirect(`/#/verify?token=${req.query.token}`));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user