feat: add Hono HTTP server with REST API
This commit is contained in:
parent
002f6dc8f3
commit
0235473254
115
src/api/routes.ts
Normal file
115
src/api/routes.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { Hono } from 'hono';
|
||||||
|
import { parseTemplate } from '../engine/parser';
|
||||||
|
import { renderTemplate } from '../engine/render';
|
||||||
|
import { extractSchema } from '../engine/schema';
|
||||||
|
import { PrinterConnector } from '../printer/connector';
|
||||||
|
import type { Template } from '../types/template';
|
||||||
|
|
||||||
|
const templates = new Map<string, Template>();
|
||||||
|
let printerConnector: PrinterConnector | null = null;
|
||||||
|
|
||||||
|
function getPrinter(): PrinterConnector {
|
||||||
|
if (!printerConnector) {
|
||||||
|
const ip = process.env.PRINTER_IP || '192.168.1.100';
|
||||||
|
const port = parseInt(process.env.PRINTER_PORT || '9100');
|
||||||
|
printerConnector = new PrinterConnector({ ip, port });
|
||||||
|
}
|
||||||
|
return printerConnector;
|
||||||
|
}
|
||||||
|
|
||||||
|
const api = new Hono();
|
||||||
|
|
||||||
|
// 模板管理
|
||||||
|
api.get('/templates', (c) => {
|
||||||
|
const list = Array.from(templates.values()).map(t => ({
|
||||||
|
id: t.id,
|
||||||
|
name: t.name,
|
||||||
|
description: t.description,
|
||||||
|
}));
|
||||||
|
return c.json({ success: true, templates: list });
|
||||||
|
});
|
||||||
|
|
||||||
|
api.get('/templates/:id', (c) => {
|
||||||
|
const id = c.req.param('id');
|
||||||
|
const template = templates.get(id);
|
||||||
|
if (!template) {
|
||||||
|
return c.json({ success: false, error: { code: 'TEMPLATE_NOT_FOUND', message: `Template '${id}' not found` }}, 404);
|
||||||
|
}
|
||||||
|
return c.json({ success: true, template });
|
||||||
|
});
|
||||||
|
|
||||||
|
api.post('/templates', async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
if (!body.id || !body.config) {
|
||||||
|
return c.json({ success: false, error: { code: 'INVALID_REQUEST', message: 'Missing id or config' }}, 400);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const config = typeof body.config === 'string' ? parseTemplate(body.config) : body.config;
|
||||||
|
const template: Template = { ...config, id: body.id };
|
||||||
|
templates.set(body.id, template);
|
||||||
|
return c.json({ success: true, template: { id: template.id, name: template.name }}, 201);
|
||||||
|
} catch (error) {
|
||||||
|
return c.json({ success: false, error: { code: 'INVALID_TEMPLATE', message: String(error) }}, 400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
api.get('/templates/:id/schema', (c) => {
|
||||||
|
const id = c.req.param('id');
|
||||||
|
const template = templates.get(id);
|
||||||
|
if (!template) {
|
||||||
|
return c.json({ success: false, error: { code: 'TEMPLATE_NOT_FOUND', message: `Template '${id}' not found` }}, 404);
|
||||||
|
}
|
||||||
|
const { schema, example } = extractSchema(template);
|
||||||
|
return c.json({ success: true, templateId: id, schema, example });
|
||||||
|
});
|
||||||
|
|
||||||
|
// 打印
|
||||||
|
api.post('/print/:templateId', async (c) => {
|
||||||
|
const templateId = c.req.param('templateId');
|
||||||
|
const template = templates.get(templateId);
|
||||||
|
if (!template) {
|
||||||
|
return c.json({ success: false, error: { code: 'TEMPLATE_NOT_FOUND', message: `Template '${templateId}' not found` }}, 404);
|
||||||
|
}
|
||||||
|
const body = await c.req.json();
|
||||||
|
const { data = {} } = body;
|
||||||
|
try {
|
||||||
|
const escData = renderTemplate(template, data);
|
||||||
|
const printer = getPrinter();
|
||||||
|
const jobId = printer.queue(escData);
|
||||||
|
return c.json({ success: true, jobId, status: 'queued', estimatedTime: '5s' });
|
||||||
|
} catch (error) {
|
||||||
|
return c.json({ success: false, error: { code: 'RENDER_ERROR', message: String(error) }}, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 打印机状态
|
||||||
|
api.get('/printer/status', async (c) => {
|
||||||
|
const printer = getPrinter();
|
||||||
|
const status = await printer.getStatus();
|
||||||
|
return c.json({
|
||||||
|
success: true,
|
||||||
|
printer: {
|
||||||
|
ip: process.env.PRINTER_IP || '192.168.1.100',
|
||||||
|
port: parseInt(process.env.PRINTER_PORT || '9100'),
|
||||||
|
...status,
|
||||||
|
queueLength: printer.getQueueLength()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
api.post('/printer/test', (c) => {
|
||||||
|
const printer = getPrinter();
|
||||||
|
const testData = new Uint8Array([
|
||||||
|
0x1B, 0x40,
|
||||||
|
0x1B, 0x61, 0x01,
|
||||||
|
...new TextEncoder().encode('Printer Test Page\n'),
|
||||||
|
...new TextEncoder().encode('================\n'),
|
||||||
|
0x1B, 0x61, 0x00,
|
||||||
|
0x0A, 0x0A,
|
||||||
|
0x1D, 0x56, 0x00,
|
||||||
|
]);
|
||||||
|
const jobId = printer.queue(testData);
|
||||||
|
return c.json({ success: true, jobId, message: 'Test page queued' });
|
||||||
|
});
|
||||||
|
|
||||||
|
export { api as apiRoutes };
|
||||||
@ -1 +1,23 @@
|
|||||||
console.log("Hello via Bun!");
|
import { Hono } from 'hono';
|
||||||
|
import { serveStatic } from 'hono/bun';
|
||||||
|
import { apiRoutes } from './api/routes';
|
||||||
|
|
||||||
|
const app = new Hono();
|
||||||
|
|
||||||
|
// 静态文件服务(Web 界面)
|
||||||
|
app.use('/*', serveStatic({ root: './src/static' }));
|
||||||
|
|
||||||
|
// API 路由
|
||||||
|
app.route('/api', apiRoutes);
|
||||||
|
|
||||||
|
// 健康检查
|
||||||
|
app.get('/health', (c) => c.json({ status: 'ok' }));
|
||||||
|
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
console.log(`🖨️ Receipt Printer Server starting on http://localhost:${port}`);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
port,
|
||||||
|
fetch: app.fetch,
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user