|
|
@@ -0,0 +1,158 @@
|
|
|
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi'
|
|
|
+import { z } from '@hono/zod-openapi'
|
|
|
+import { AppDataSource } from '@/server/data-source'
|
|
|
+import { excelParser } from '@/server/utils/excelParser'
|
|
|
+import { ErrorSchema } from '@/server/utils/errorHandler'
|
|
|
+import type { AuthContext } from '@/server/types/context'
|
|
|
+
|
|
|
+// 请求Schema
|
|
|
+const ConvertRequestSchema = z.object({
|
|
|
+ templateId: z.string().optional().openapi({
|
|
|
+ example: 'template-123',
|
|
|
+ description: '模板ID'
|
|
|
+ }),
|
|
|
+ config: z.any().optional().openapi({
|
|
|
+ description: '自定义配置'
|
|
|
+ }),
|
|
|
+ input: z.string().openapi({
|
|
|
+ example: 'https://example.com/file.xlsx',
|
|
|
+ description: 'Excel文件URL或Base64编码'
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+// 响应Schema
|
|
|
+const ConvertResponseSchema = z.object({
|
|
|
+ success: z.boolean().openapi({
|
|
|
+ example: true,
|
|
|
+ description: '是否成功'
|
|
|
+ }),
|
|
|
+ data: z.any().openapi({
|
|
|
+ description: '转换后的JSON数据'
|
|
|
+ }),
|
|
|
+ warnings: z.array(z.string()).openapi({
|
|
|
+ example: ['Sheet1: 缺少必填字段'],
|
|
|
+ description: '转换过程中的警告信息'
|
|
|
+ }),
|
|
|
+ availableFields: z.record(z.array(z.string())).openapi({
|
|
|
+ description: '各工作表可用字段'
|
|
|
+ }),
|
|
|
+ totalTables: z.number().openapi({
|
|
|
+ example: 3,
|
|
|
+ description: '总表格数'
|
|
|
+ })
|
|
|
+})
|
|
|
+
|
|
|
+// API密钥验证中间件
|
|
|
+const apiKeyMiddleware = async (c: any, next: any) => {
|
|
|
+ const apiKey = c.req.header('X-API-Key')
|
|
|
+ if (!apiKey) {
|
|
|
+ return c.json({
|
|
|
+ code: 401,
|
|
|
+ message: '缺少API密钥'
|
|
|
+ }, 401)
|
|
|
+ }
|
|
|
+
|
|
|
+ const validApiKey = process.env.API_KEY || 'excel2json-api-key'
|
|
|
+ if (apiKey !== validApiKey) {
|
|
|
+ return c.json({
|
|
|
+ code: 401,
|
|
|
+ message: 'API密钥无效'
|
|
|
+ }, 401)
|
|
|
+ }
|
|
|
+
|
|
|
+ await next()
|
|
|
+}
|
|
|
+
|
|
|
+// 路由定义
|
|
|
+const routeDef = createRoute({
|
|
|
+ method: 'post',
|
|
|
+ path: '/',
|
|
|
+ middleware: [apiKeyMiddleware],
|
|
|
+ request: {
|
|
|
+ body: {
|
|
|
+ content: {
|
|
|
+ 'application/json': { schema: ConvertRequestSchema }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ responses: {
|
|
|
+ 200: {
|
|
|
+ description: '转换成功',
|
|
|
+ content: { 'application/json': { schema: ConvertResponseSchema } }
|
|
|
+ },
|
|
|
+ 400: {
|
|
|
+ description: '客户端错误',
|
|
|
+ content: { 'application/json': { schema: ErrorSchema } }
|
|
|
+ },
|
|
|
+ 401: {
|
|
|
+ description: '认证错误',
|
|
|
+ content: { 'application/json': { schema: ErrorSchema } }
|
|
|
+ },
|
|
|
+ 404: {
|
|
|
+ description: '模板不存在',
|
|
|
+ content: { 'application/json': { schema: ErrorSchema } }
|
|
|
+ },
|
|
|
+ 500: {
|
|
|
+ description: '服务器错误',
|
|
|
+ content: { 'application/json': { schema: ErrorSchema } }
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+// 路由实现
|
|
|
+const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
|
|
|
+ try {
|
|
|
+ const { templateId, config, input } = c.req.valid('json')
|
|
|
+
|
|
|
+ if (!input) {
|
|
|
+ return c.json({
|
|
|
+ code: 400,
|
|
|
+ message: '请提供input参数'
|
|
|
+ }, 400)
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!templateId && !config) {
|
|
|
+ return c.json({
|
|
|
+ code: 400,
|
|
|
+ message: '请提供templateId或config参数'
|
|
|
+ }, 400)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取模板配置
|
|
|
+ let templateConfig
|
|
|
+ if (templateId) {
|
|
|
+ const template = await AppDataSource.getRepository('ExcelTemplate')
|
|
|
+ .findOne({ where: { id: templateId, isDeleted: 0 } })
|
|
|
+
|
|
|
+ if (!template) {
|
|
|
+ return c.json({
|
|
|
+ code: 404,
|
|
|
+ message: '模板不存在'
|
|
|
+ }, 404)
|
|
|
+ }
|
|
|
+ templateConfig = template.templateConfig
|
|
|
+ } else {
|
|
|
+ templateConfig = config
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析Excel
|
|
|
+ const buffer = await excelParser.getBufferFromUrlOrBase64(input)
|
|
|
+ const result = await excelParser.parseExcelBuffer(buffer, templateConfig.sheets)
|
|
|
+
|
|
|
+ return c.json({
|
|
|
+ success: true,
|
|
|
+ data: result.exportData,
|
|
|
+ warnings: result.warnings,
|
|
|
+ availableFields: result.availableFieldsBySheet,
|
|
|
+ totalTables: result.totalTables
|
|
|
+ }, 200)
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ return c.json({
|
|
|
+ code: 500,
|
|
|
+ message: error instanceof Error ? error.message : '转换失败'
|
|
|
+ }, 500)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+export default app
|