From 023547325466ea44131da799ddbf1e0b920e72aa Mon Sep 17 00:00:00 2001 From: Developer Date: Thu, 12 Feb 2026 08:07:58 +0000 Subject: [PATCH] feat: add Hono HTTP server with REST API --- src/api/routes.ts | 115 ++++++++++++++++++++++++++++++++++++++++++++++ src/server.ts | 24 +++++++++- 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/api/routes.ts diff --git a/src/api/routes.ts b/src/api/routes.ts new file mode 100644 index 0000000..9cfcd47 --- /dev/null +++ b/src/api/routes.ts @@ -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(); +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 }; \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index f67b2c6..c080774 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1 +1,23 @@ -console.log("Hello via Bun!"); \ No newline at end of file +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, +}; \ No newline at end of file