Browse Source

迁移 转换 api

yourname 5 months ago
parent
commit
d153e16c59
4 changed files with 174 additions and 1 deletions
  1. 5 1
      src/client/api.ts
  2. 3 0
      src/server/api.ts
  3. 8 0
      src/server/api/convert/index.ts
  4. 158 0
      src/server/api/convert/post.ts

+ 5 - 1
src/client/api.ts

@@ -1,7 +1,7 @@
 import axios, { isAxiosError } from 'axios';
 import axios, { isAxiosError } from 'axios';
 import { hc } from 'hono/client'
 import { hc } from 'hono/client'
 import type {
 import type {
-  AuthRoutes, ExcelTemplatesRoutes, UserRoutes,
+  AuthRoutes, ConvertRoutes, ExcelTemplatesRoutes, UserRoutes,
 } from '@/server/api';
 } from '@/server/api';
 
 
 // 创建 axios 适配器
 // 创建 axios 适配器
@@ -64,3 +64,7 @@ export const userClient = hc<UserRoutes>('/', {
 export const excelTemplateClient = hc<ExcelTemplatesRoutes>('/', {
 export const excelTemplateClient = hc<ExcelTemplatesRoutes>('/', {
   fetch: axiosFetch,
   fetch: axiosFetch,
 }).api.v1['excel-templates'];
 }).api.v1['excel-templates'];
+
+export const convertClient = hc<ConvertRoutes>('/', {
+  fetch: axiosFetch,
+}).api.v1['convert'];

+ 3 - 0
src/server/api.ts

@@ -54,13 +54,16 @@ if(!import.meta.env.PROD){
 
 
 
 
 import excelTemplatesRoute from './api/excel-templates/index'
 import excelTemplatesRoute from './api/excel-templates/index'
+import convertRoute from './api/convert/index'
 
 
 const userRoutes = api.route('/api/v1/users', usersRouter)
 const userRoutes = api.route('/api/v1/users', usersRouter)
 const authRoutes = api.route('/api/v1/auth', authRoute)
 const authRoutes = api.route('/api/v1/auth', authRoute)
 const excelTemplatesRoutes = api.route('/api/v1/excel-templates', excelTemplatesRoute)
 const excelTemplatesRoutes = api.route('/api/v1/excel-templates', excelTemplatesRoute)
+const convertRoutes = api.route('/api/v1/convert', convertRoute)
 
 
 export type AuthRoutes = typeof authRoutes
 export type AuthRoutes = typeof authRoutes
 export type UserRoutes = typeof userRoutes
 export type UserRoutes = typeof userRoutes
 export type ExcelTemplatesRoutes = typeof excelTemplatesRoutes
 export type ExcelTemplatesRoutes = typeof excelTemplatesRoutes
+export type tRoutes =  = typeof convertRoutes
 
 
 export default api
 export default api

+ 8 - 0
src/server/api/convert/index.ts

@@ -0,0 +1,8 @@
+import { OpenAPIHono } from '@hono/zod-openapi'
+import convertRoute from './post'
+import type { AuthContext } from '@/server/types/context'
+
+const app = new OpenAPIHono<AuthContext>()
+  .route('/', convertRoute)
+
+export default app

+ 158 - 0
src/server/api/convert/post.ts

@@ -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