Просмотр исходного кода

Merge remote-tracking branch 'upstream/bmad-method' into bmad-method

yourname 4 месяцев назад
Родитель
Сommit
6b97a9285e
43 измененных файлов с 76 добавлено и 93 удалено
  1. 1 0
      .claude/settings.local.json
  2. 1 0
      src/client/__test_utils__/test-render.tsx
  3. 3 3
      src/client/admin/components/AvatarSelector.tsx
  4. 0 2
      src/client/admin/components/ErrorPage.tsx
  5. 2 4
      src/client/admin/components/FileSelector.tsx
  6. 1 14
      src/client/admin/components/MinioUploader.tsx
  7. 0 1
      src/client/admin/components/NotFoundPage.tsx
  8. 3 3
      src/client/admin/hooks/AuthProvider.tsx
  9. 0 1
      src/client/admin/pages/Dashboard.tsx
  10. 7 7
      src/client/admin/pages/Files.tsx
  11. 0 4
      src/client/admin/pages/__tests__/Users.test.tsx
  12. 0 1
      src/client/admin/routes.tsx
  13. 0 2
      src/client/home/components/ErrorPage.tsx
  14. 0 1
      src/client/home/components/NotFoundPage.tsx
  15. 3 3
      src/client/home/hooks/AuthProvider.tsx
  16. 0 1
      src/client/home/index.tsx
  17. 0 1
      src/client/home/layouts/MainLayout.tsx
  18. 1 3
      src/client/home/pages/LoginPage.tsx
  19. 2 3
      src/client/home/pages/MemberPage.tsx
  20. 1 2
      src/client/home/pages/RegisterPage.tsx
  21. 1 2
      src/client/home/routes.tsx
  22. 1 1
      src/client/utils/minio.ts
  23. 21 9
      src/server/__integration_tests__/minio.integration.test.ts
  24. 1 1
      src/server/__test_utils__/service-mocks.ts
  25. 2 3
      src/server/api.ts
  26. 1 1
      src/server/api/auth/__tests__/auth.integration.test.ts
  27. 3 1
      src/server/api/auth/login/password.ts
  28. 0 1
      src/server/api/auth/sso-verify.ts
  29. 0 1
      src/server/api/files/[id]/get-url.ts
  30. 6 2
      src/server/api/files/multipart-complete/post.ts
  31. 0 1
      src/server/api/files/multipart-policy/post.ts
  32. 0 1
      src/server/api/roles/index.ts
  33. 2 2
      src/server/api/users/__tests__/users.integration.test.ts
  34. 3 2
      src/server/api/users/custom.ts
  35. 2 2
      src/server/modules/files/__tests__/minio.service.test.ts
  36. 1 1
      src/server/modules/users/role.service.ts
  37. 0 1
      src/server/renderer.tsx
  38. 0 2
      src/server/utils/__tests__/backup.test.ts
  39. 0 1
      src/server/utils/__tests__/restore.test.ts
  40. 4 0
      src/server/utils/generic-crud.routes.ts
  41. 1 1
      src/server/utils/generic-crud.service.ts
  42. 1 1
      src/server/utils/parseWithAwait.ts
  43. 1 0
      src/test/test-utils.ts

+ 1 - 0
.claude/settings.local.json

@@ -29,6 +29,7 @@
       "Bash(pnpm run db:backup:latest:*)",
       "Bash(pnpm run db:backup:latest:*)",
       "Bash(done)",
       "Bash(done)",
       "Bash(do sed -i '8d' \"$file\")",
       "Bash(do sed -i '8d' \"$file\")",
+      "Bash(pnpm typecheck)",
       "Bash(pnpm test:components)",
       "Bash(pnpm test:components)",
       "Bash(pnpm test:components:*)",
       "Bash(pnpm test:components:*)",
       "Bash(eslint:*)",
       "Bash(eslint:*)",

+ 1 - 0
src/client/__test_utils__/test-render.tsx

@@ -3,6 +3,7 @@ import { BrowserRouter } from 'react-router-dom';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
 import { ThemeProvider } from 'next-themes';
 import { ThemeProvider } from 'next-themes';
 import { AuthProvider } from '@/client/admin/hooks/AuthProvider';
 import { AuthProvider } from '@/client/admin/hooks/AuthProvider';
+import { vi } from 'vitest';
 
 
 /**
 /**
  * 创建测试用的QueryClient
  * 创建测试用的QueryClient

+ 3 - 3
src/client/admin/components/AvatarSelector.tsx

@@ -31,7 +31,7 @@ const AvatarSelector: React.FC<AvatarSelectorProps> = ({
   accept = 'image/*',
   accept = 'image/*',
   maxSize = 2,
   maxSize = 2,
   uploadPath = '/avatars',
   uploadPath = '/avatars',
-  uploadButtonText = '上传头像',
+  uploadButtonText = '上传',
   previewSize = 'medium',
   previewSize = 'medium',
   showPreview = true,
   showPreview = true,
   placeholder = '选择头像',
   placeholder = '选择头像',
@@ -44,7 +44,7 @@ const AvatarSelector: React.FC<AvatarSelectorProps> = ({
     queryKey: ['file-detail', value],
     queryKey: ['file-detail', value],
     queryFn: async () => {
     queryFn: async () => {
       if (!value) return null;
       if (!value) return null;
-      const response = await fileClient[':id']['$get']({ param: { id: value.toString() } });
+      const response = await fileClient[':id']['$get']({ param: { id: Number(value) } });
       if (response.status !== 200) throw new Error('获取文件详情失败');
       if (response.status !== 200) throw new Error('获取文件详情失败');
       return response.json();
       return response.json();
     },
     },
@@ -220,7 +220,7 @@ const AvatarSelector: React.FC<AvatarSelectorProps> = ({
                           accept={accept}
                           accept={accept}
                           maxSize={maxSize}
                           maxSize={maxSize}
                           onUploadSuccess={handleUploadSuccess}
                           onUploadSuccess={handleUploadSuccess}
-                          buttonText="上传"
+                          buttonText={uploadButtonText}
                           size="minimal"
                           size="minimal"
                           displayMode="card"
                           displayMode="card"
                           showUploadList={false}
                           showUploadList={false}

+ 0 - 2
src/client/admin/components/ErrorPage.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 import { useRouteError, useNavigate } from 'react-router';
 import { useRouteError, useNavigate } from 'react-router';
 import { Alert, AlertDescription, AlertTitle } from '@/client/components/ui/alert';
 import { Alert, AlertDescription, AlertTitle } from '@/client/components/ui/alert';
 import { Button } from '@/client/components/ui/button';
 import { Button } from '@/client/components/ui/button';
@@ -7,7 +6,6 @@ import { AlertCircle } from 'lucide-react';
 export const ErrorPage = () => {
 export const ErrorPage = () => {
   const navigate = useNavigate();
   const navigate = useNavigate();
   const error = useRouteError() as any;
   const error = useRouteError() as any;
-  const errorMessage = error?.statusText || error?.message || '未知错误';
   
   
   return (
   return (
     <div className="flex flex-col items-center justify-center flex-grow p-4">
     <div className="flex flex-col items-center justify-center flex-grow p-4">

+ 2 - 4
src/client/admin/components/FileSelector.tsx

@@ -18,7 +18,6 @@ export interface FileSelectorProps {
   accept?: string;
   accept?: string;
   maxSize?: number;
   maxSize?: number;
   uploadPath?: string;
   uploadPath?: string;
-  uploadButtonText?: string;
   previewSize?: 'small' | 'medium' | 'large';
   previewSize?: 'small' | 'medium' | 'large';
   showPreview?: boolean;
   showPreview?: boolean;
   placeholder?: string;
   placeholder?: string;
@@ -34,7 +33,6 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
   accept = '*/*',
   accept = '*/*',
   maxSize = 10,
   maxSize = 10,
   uploadPath = '/files',
   uploadPath = '/files',
-  uploadButtonText = '上传文件',
   previewSize = 'medium',
   previewSize = 'medium',
   showPreview = true,
   showPreview = true,
   placeholder = '选择文件',
   placeholder = '选择文件',
@@ -60,7 +58,7 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
         // 批量获取多个文件详情
         // 批量获取多个文件详情
         const filePromises = value.map(async (fileId) => {
         const filePromises = value.map(async (fileId) => {
           try {
           try {
-            const response = await fileClient[':id']['$get']({ param: { id: fileId.toString() } });
+            const response = await fileClient[':id']['$get']({ param: { id: Number(fileId) } });
             if (response.status === 200) {
             if (response.status === 200) {
               return response.json();
               return response.json();
             }
             }
@@ -77,7 +75,7 @@ export const FileSelector: React.FC<FileSelectorProps> = ({
       
       
       // 处理单选模式下的单值
       // 处理单选模式下的单值
       if (!Array.isArray(value)) {
       if (!Array.isArray(value)) {
-        const response = await fileClient[':id']['$get']({ param: { id: value.toString() } });
+        const response = await fileClient[':id']['$get']({ param: { id: Number(value) } });
         if (response.status !== 200) throw new Error('获取文件详情失败');
         if (response.status !== 200) throw new Error('获取文件详情失败');
         return [await response.json()];
         return [await response.json()];
       }
       }

+ 1 - 14
src/client/admin/components/MinioUploader.tsx

@@ -64,7 +64,6 @@ const MinioUploader: React.FC<MinioUploaderProps> = ({
   displayMode = 'full'
   displayMode = 'full'
 }) => {
 }) => {
   const [fileList, setFileList] = useState<UploadFile[]>([]);
   const [fileList, setFileList] = useState<UploadFile[]>([]);
-  const [uploadingFiles, setUploadingFiles] = useState<Set<string>>(new Set());
   const [dragActive, setDragActive] = useState(false);
   const [dragActive, setDragActive] = useState(false);
 
 
   // 根据尺寸模式获取样式配置
   // 根据尺寸模式获取样式配置
@@ -142,11 +141,6 @@ const MinioUploader: React.FC<MinioUploaderProps> = ({
       })
       })
     );
     );
     
     
-    setUploadingFiles(prev => {
-      const newSet = new Set(prev);
-      newSet.delete(uid);
-      return newSet;
-    });
     
     
     // toast.success(`文件 "${file.name}" 上传成功`);
     // toast.success(`文件 "${file.name}" 上传成功`);
     onUploadSuccess?.(result.fileKey, result.fileUrl, file);
     onUploadSuccess?.(result.fileKey, result.fileUrl, file);
@@ -168,11 +162,6 @@ const MinioUploader: React.FC<MinioUploaderProps> = ({
       })
       })
     );
     );
     
     
-    setUploadingFiles(prev => {
-      const newSet = new Set(prev);
-      newSet.delete(uid);
-      return newSet;
-    });
     
     
     // toast.error(`文件 "${file.name}" 上传失败: ${error.message}`);
     // toast.error(`文件 "${file.name}" 上传失败: ${error.message}`);
     onUploadError?.(error, file);
     onUploadError?.(error, file);
@@ -180,7 +169,7 @@ const MinioUploader: React.FC<MinioUploaderProps> = ({
 
 
   // 自定义上传逻辑
   // 自定义上传逻辑
   const handleUpload = async (file: File) => {
   const handleUpload = async (file: File) => {
-    const uid = Date.now().toString() + Math.random().toString(36).substr(2, 9);
+    const uid = Date.now().toString() + Math.random().toString(36).substring(2, 11);
     
     
     // 添加到文件列表
     // 添加到文件列表
     setFileList(prev => [
     setFileList(prev => [
@@ -195,8 +184,6 @@ const MinioUploader: React.FC<MinioUploaderProps> = ({
       }
       }
     ]);
     ]);
     
     
-    // 添加到上传中集合
-    setUploadingFiles(prev => new Set(prev).add(uid));
     
     
     try {
     try {
       // 验证文件大小
       // 验证文件大小

+ 0 - 1
src/client/admin/components/NotFoundPage.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 import { useNavigate } from 'react-router';
 import { useNavigate } from 'react-router';
 import { Button } from '@/client/components/ui/button';
 import { Button } from '@/client/components/ui/button';
 
 

+ 3 - 3
src/client/admin/hooks/AuthProvider.tsx

@@ -1,4 +1,4 @@
-import React, { useState, useEffect, createContext, useContext } from 'react';
+import React, { useState, createContext, useContext } from 'react';
 
 
 import {
 import {
   useQuery,
   useQuery,
@@ -10,7 +10,7 @@ import type {
   AuthContextType
   AuthContextType
 } from '@/share/types';
 } from '@/share/types';
 import { authClient } from '@/client/api';
 import { authClient } from '@/client/api';
-import type { InferResponseType, InferRequestType } from 'hono/client';
+import type { InferResponseType } from 'hono/client';
 
 
 type User = InferResponseType<typeof authClient.me.$get, 200>;
 type User = InferResponseType<typeof authClient.me.$get, 200>;
 
 
@@ -80,7 +80,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
     retry: false
     retry: false
   });
   });
 
 
-  const handleLogin = async (username: string, password: string, latitude?: number, longitude?: number): Promise<void> => {
+  const handleLogin = async (username: string, password: string): Promise<void> => {
     try {
     try {
       // 使用AuthAPI登录
       // 使用AuthAPI登录
       const response = await authClient.login.$post({
       const response = await authClient.login.$post({

+ 0 - 1
src/client/admin/pages/Dashboard.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 import { useNavigate } from 'react-router';
 import { useNavigate } from 'react-router';
 import { Users, Bell, Eye, TrendingUp, TrendingDown, Activity } from 'lucide-react';
 import { Users, Bell, Eye, TrendingUp, TrendingDown, Activity } from 'lucide-react';
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';

+ 7 - 7
src/client/admin/pages/Files.tsx

@@ -64,7 +64,7 @@ export const FilesPage: React.FC = () => {
   // 更新文件记录
   // 更新文件记录
   const updateFile = useMutation({
   const updateFile = useMutation({
     mutationFn: ({ id, data }: { id: number; data: UpdateFileRequest }) =>
     mutationFn: ({ id, data }: { id: number; data: UpdateFileRequest }) =>
-      fileClient[':id'].$put({ param: { id: id.toString() }, json: data }),
+      fileClient[':id'].$put({ param: { id: Number(id) }, json: data }),
     onSuccess: () => {
     onSuccess: () => {
       toast.success('文件记录更新成功');
       toast.success('文件记录更新成功');
       queryClient.invalidateQueries({ queryKey: ['files'] });
       queryClient.invalidateQueries({ queryKey: ['files'] });
@@ -78,7 +78,7 @@ export const FilesPage: React.FC = () => {
 
 
   // 删除文件记录
   // 删除文件记录
   const deleteFile = useMutation({
   const deleteFile = useMutation({
-    mutationFn: (id: number) => fileClient[':id'].$delete({ param: { id: id.toString() } }),
+    mutationFn: (id: number) => fileClient[':id'].$delete({ param: { id: Number(id) } }),
     onSuccess: () => {
     onSuccess: () => {
       toast.success('文件记录删除成功');
       toast.success('文件记录删除成功');
       queryClient.invalidateQueries({ queryKey: ['files'] });
       queryClient.invalidateQueries({ queryKey: ['files'] });
@@ -114,13 +114,13 @@ export const FilesPage: React.FC = () => {
   };
   };
 
 
   // 处理上传成功回调
   // 处理上传成功回调
-  const handleUploadSuccess = (fileKey: string, fileUrl: string, file: File) => {
+  const handleUploadSuccess = () => {
     toast.success('文件上传成功');
     toast.success('文件上传成功');
     queryClient.invalidateQueries({ queryKey: ['files'] });
     queryClient.invalidateQueries({ queryKey: ['files'] });
   };
   };
 
 
   // 处理上传失败回调
   // 处理上传失败回调
-  const handleUploadError = (error: Error, file: File) => {
+  const handleUploadError = (error: Error) => {
     toast.error(`上传失败: ${error instanceof Error ? error.message : '未知错误'}`);
     toast.error(`上传失败: ${error instanceof Error ? error.message : '未知错误'}`);
   };
   };
 
 
@@ -206,7 +206,7 @@ export const FilesPage: React.FC = () => {
                 placeholder="搜索文件名称或类型"
                 placeholder="搜索文件名称或类型"
                 value={searchText}
                 value={searchText}
                 onChange={(e) => setSearchText(e.target.value)}
                 onChange={(e) => setSearchText(e.target.value)}
-                onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
+                onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
                 className="max-w-sm"
                 className="max-w-sm"
               />
               />
             </div>
             </div>
@@ -377,8 +377,8 @@ export const FilesPage: React.FC = () => {
               uploadPath="/files"
               uploadPath="/files"
               maxSize={500}
               maxSize={500}
               multiple={false}
               multiple={false}
-              onUploadSuccess={(fileKey, fileUrl, file) => {
-                handleUploadSuccess(fileKey, fileUrl, file);
+              onUploadSuccess={() => {
+                handleUploadSuccess();
                 setIsUploadModalOpen(false);
                 setIsUploadModalOpen(false);
               }}
               }}
               onUploadError={handleUploadError}
               onUploadError={handleUploadError}

+ 0 - 4
src/client/admin/pages/__tests__/Users.test.tsx

@@ -421,8 +421,6 @@ describe('UsersPage Component', () => {
   });
   });
 
 
   it.skip('应该处理删除用户操作', async () => {
   it.skip('应该处理删除用户操作', async () => {
-    const user = userEvent.setup();
-
     // 模拟删除成功
     // 模拟删除成功
     (userClient[':id']['$delete'] as any).mockResolvedValue({
     (userClient[':id']['$delete'] as any).mockResolvedValue({
       status: 204,
       status: 204,
@@ -450,8 +448,6 @@ describe('UsersPage Component', () => {
   });
   });
 
 
   it.skip('应该处理编辑用户操作', async () => {
   it.skip('应该处理编辑用户操作', async () => {
-    const user = userEvent.setup();
-
     // 模拟更新成功
     // 模拟更新成功
     (userClient[':id']['$put'] as any).mockResolvedValue({
     (userClient[':id']['$put'] as any).mockResolvedValue({
       status: 200,
       status: 200,

+ 0 - 1
src/client/admin/routes.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 import { createBrowserRouter, Navigate } from 'react-router';
 import { createBrowserRouter, Navigate } from 'react-router';
 import { ProtectedRoute } from './components/ProtectedRoute';
 import { ProtectedRoute } from './components/ProtectedRoute';
 import { MainLayout } from './layouts/MainLayout';
 import { MainLayout } from './layouts/MainLayout';

+ 0 - 2
src/client/home/components/ErrorPage.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 import { useRouteError, useNavigate } from 'react-router';
 import { useRouteError, useNavigate } from 'react-router';
 import { AlertCircle, RotateCcw, Home } from 'lucide-react';
 import { AlertCircle, RotateCcw, Home } from 'lucide-react';
 import { Button } from '@/client/components/ui/button';
 import { Button } from '@/client/components/ui/button';
@@ -8,7 +7,6 @@ import { Alert, AlertDescription, AlertTitle } from '@/client/components/ui/aler
 export const ErrorPage = () => {
 export const ErrorPage = () => {
   const navigate = useNavigate();
   const navigate = useNavigate();
   const error = useRouteError() as any;
   const error = useRouteError() as any;
-  const errorMessage = error?.statusText || error?.message || '未知错误';
   
   
   return (
   return (
     <div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 p-4">
     <div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 p-4">

+ 0 - 1
src/client/home/components/NotFoundPage.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 import { useNavigate } from 'react-router';
 import { useNavigate } from 'react-router';
 import { ArrowLeft, Home } from 'lucide-react';
 import { ArrowLeft, Home } from 'lucide-react';
 import { Button } from '@/client/components/ui/button';
 import { Button } from '@/client/components/ui/button';

+ 3 - 3
src/client/home/hooks/AuthProvider.tsx

@@ -1,4 +1,4 @@
-import React, { useState, useEffect, createContext, useContext } from 'react';
+import React, { useState, createContext, useContext } from 'react';
 
 
 import {
 import {
   useQuery,
   useQuery,
@@ -10,7 +10,7 @@ import type {
   AuthContextType
   AuthContextType
 } from '@/share/types';
 } from '@/share/types';
 import { authClient } from '@/client/api';
 import { authClient } from '@/client/api';
-import type { InferResponseType, InferRequestType } from 'hono/client';
+import type { InferResponseType } from 'hono/client';
 
 
 export type User = InferResponseType<typeof authClient.me.$get, 200>;
 export type User = InferResponseType<typeof authClient.me.$get, 200>;
 
 
@@ -80,7 +80,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
     retry: false
     retry: false
   });
   });
 
 
-  const handleLogin = async (username: string, password: string, latitude?: number, longitude?: number): Promise<void> => {
+  const handleLogin = async (username: string, password: string): Promise<void> => {
     try {
     try {
       // 使用AuthAPI登录
       // 使用AuthAPI登录
       const response = await authClient.login.$post({
       const response = await authClient.login.$post({

+ 0 - 1
src/client/home/index.tsx

@@ -1,5 +1,4 @@
 import { createRoot } from 'react-dom/client'
 import { createRoot } from 'react-dom/client'
-import { getGlobalConfig } from '../utils/utils'
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
 import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
 import { AuthProvider } from './hooks/AuthProvider'
 import { AuthProvider } from './hooks/AuthProvider'
 import { RouterProvider } from 'react-router-dom'
 import { RouterProvider } from 'react-router-dom'

+ 0 - 1
src/client/home/layouts/MainLayout.tsx

@@ -1,4 +1,3 @@
-import React, { useState, useEffect, useMemo } from 'react';
 import {
 import {
   Outlet
   Outlet
 } from 'react-router';
 } from 'react-router';

+ 1 - 3
src/client/home/pages/LoginPage.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 import { useForm } from 'react-hook-form';
 import { useForm } from 'react-hook-form';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { z } from 'zod';
 import { z } from 'zod';
@@ -8,10 +7,9 @@ import { toast } from 'sonner';
 
 
 import { Button } from '@/client/components/ui/button';
 import { Button } from '@/client/components/ui/button';
 import { Input } from '@/client/components/ui/input';
 import { Input } from '@/client/components/ui/input';
-import { Label } from '@/client/components/ui/label';
 import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
 import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
 import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
 import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
-import { Eye, EyeOff, User, Lock } from 'lucide-react';
+import { User, Lock } from 'lucide-react';
 
 
 const loginSchema = z.object({
 const loginSchema = z.object({
   username: z.string().min(3, '用户名至少3个字符'),
   username: z.string().min(3, '用户名至少3个字符'),

+ 2 - 3
src/client/home/pages/MemberPage.tsx

@@ -4,7 +4,6 @@ import { useAuth } from '@/client/home/hooks/AuthProvider';
 import { Button } from '@/client/components/ui/button';
 import { Button } from '@/client/components/ui/button';
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
 import { Avatar, AvatarFallback, AvatarImage } from '@/client/components/ui/avatar';
 import { Avatar, AvatarFallback, AvatarImage } from '@/client/components/ui/avatar';
-import { Badge } from '@/client/components/ui/badge';
 import { Separator } from '@/client/components/ui/separator';
 import { Separator } from '@/client/components/ui/separator';
 import { 
 import { 
   User, 
   User, 
@@ -51,8 +50,8 @@ const MemberPage: React.FC = () => {
             <CardContent className="pt-6">
             <CardContent className="pt-6">
               <div className="flex flex-col items-center space-y-4">
               <div className="flex flex-col items-center space-y-4">
                 <Avatar className="h-24 w-24">
                 <Avatar className="h-24 w-24">
-                  <AvatarImage 
-                    src={user.avatar || `https://avatar.vercel.sh/${user.username}`} 
+                  <AvatarImage
+                    src={user.avatarFile?.fullUrl || `https://avatar.vercel.sh/${user.username}`}
                     alt={user.nickname || user.username}
                     alt={user.nickname || user.username}
                   />
                   />
                   <AvatarFallback className="text-2xl bg-primary text-primary-foreground">
                   <AvatarFallback className="text-2xl bg-primary text-primary-foreground">

+ 1 - 2
src/client/home/pages/RegisterPage.tsx

@@ -1,4 +1,3 @@
-import React from 'react';
 import { useForm } from 'react-hook-form';
 import { useForm } from 'react-hook-form';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { z } from 'zod';
 import { z } from 'zod';
@@ -11,7 +10,7 @@ import { Button } from '@/client/components/ui/button';
 import { Input } from '@/client/components/ui/input';
 import { Input } from '@/client/components/ui/input';
 import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
 import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
 import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
 import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
-import { Eye, EyeOff, User, Lock } from 'lucide-react';
+import { User, Lock } from 'lucide-react';
 
 
 const registerSchema = z.object({
 const registerSchema = z.object({
   username: z.string()
   username: z.string()

+ 1 - 2
src/client/home/routes.tsx

@@ -1,5 +1,4 @@
-import React from 'react';
-import { createBrowserRouter, Navigate } from 'react-router';
+import { createBrowserRouter } from 'react-router';
 import { ProtectedRoute } from './components/ProtectedRoute';
 import { ProtectedRoute } from './components/ProtectedRoute';
 import { ErrorPage } from './components/ErrorPage';
 import { ErrorPage } from './components/ErrorPage';
 import { NotFoundPage } from './components/NotFoundPage';
 import { NotFoundPage } from './components/NotFoundPage';

+ 1 - 1
src/client/utils/minio.ts

@@ -1,4 +1,4 @@
-import type { InferResponseType, InferRequestType } from 'hono/client';
+import type { InferResponseType } from 'hono/client';
 import { fileClient } from "../api";
 import { fileClient } from "../api";
 
 
 export interface MinioProgressEvent {
 export interface MinioProgressEvent {

+ 21 - 9
src/server/__integration_tests__/minio.integration.test.ts

@@ -7,6 +7,20 @@ import { logger } from '@/server/utils/logger';
 vi.mock('minio');
 vi.mock('minio');
 vi.mock('@/server/utils/logger');
 vi.mock('@/server/utils/logger');
 
 
+// Mock process.env using vi.stubEnv for proper isolation
+beforeEach(() => {
+  vi.stubEnv('MINIO_HOST', 'localhost');
+  vi.stubEnv('MINIO_PORT', '9000');
+  vi.stubEnv('MINIO_USE_SSL', 'false');
+  vi.stubEnv('MINIO_ACCESS_KEY', 'minioadmin');
+  vi.stubEnv('MINIO_SECRET_KEY', 'minioadmin');
+  vi.stubEnv('MINIO_BUCKET_NAME', 'test-bucket');
+});
+
+afterEach(() => {
+  vi.unstubAllEnvs();
+});
+
 describe('MinIO Integration Tests', () => {
 describe('MinIO Integration Tests', () => {
   let minioService: MinioService;
   let minioService: MinioService;
   let mockClient: Client;
   let mockClient: Client;
@@ -14,17 +28,10 @@ describe('MinIO Integration Tests', () => {
   beforeEach(() => {
   beforeEach(() => {
     mockClient = new Client({} as any);
     mockClient = new Client({} as any);
     (Client as any).mockClear();
     (Client as any).mockClear();
-
-    // Mock environment variables for testing
-    vi.stubEnv('MINIO_HOST', 'localhost');
-    vi.stubEnv('MINIO_PORT', '9000');
-    vi.stubEnv('MINIO_USE_SSL', 'false');
-    vi.stubEnv('MINIO_BUCKET_NAME', 'test-bucket');
+    (Client as any).mockImplementation(() => mockClient);
 
 
     // Create MinioService with mock client
     // Create MinioService with mock client
     minioService = new MinioService();
     minioService = new MinioService();
-    // Replace the internal client with our mock
-    minioService['client'] = mockClient;
   });
   });
 
 
   afterEach(() => {
   afterEach(() => {
@@ -211,6 +218,10 @@ describe('MinIO Integration Tests', () => {
 
 
     it('should handle file operation errors', async () => {
     it('should handle file operation errors', async () => {
       const operationError = new Error('Operation failed');
       const operationError = new Error('Operation failed');
+
+      // 确保桶存在成功
+      mockClient.bucketExists = vi.fn().mockResolvedValue(true);
+      // 但文件操作失败
       mockClient.putObject = vi.fn().mockRejectedValue(operationError);
       mockClient.putObject = vi.fn().mockRejectedValue(operationError);
 
 
       await expect(minioService.createObject(
       await expect(minioService.createObject(
@@ -274,7 +285,8 @@ describe('MinIO Integration Tests', () => {
     it('should handle large file operations', async () => {
     it('should handle large file operations', async () => {
       // Use smaller buffer size to avoid memory issues
       // Use smaller buffer size to avoid memory issues
       const largeBuffer = Buffer.alloc(1 * 1024 * 1024); // 1MB instead of 10MB
       const largeBuffer = Buffer.alloc(1 * 1024 * 1024); // 1MB instead of 10MB
-      mockClient.putObject = vi.fn().mockResolvedValue(undefined);
+      mockClient.bucketExists = vi.fn().mockResolvedValue(true);
+      mockClient.putObject = vi.fn().mockResolvedValue({ etag: 'etag123', versionId: null });
 
 
       await minioService.createObject('test-bucket', 'large-file.bin', largeBuffer, 'application/octet-stream');
       await minioService.createObject('test-bucket', 'large-file.bin', largeBuffer, 'application/octet-stream');
 
 

+ 1 - 1
src/server/__test_utils__/service-mocks.ts

@@ -133,7 +133,7 @@ export class ServiceMocks {
       if (service && typeof service === 'object') {
       if (service && typeof service === 'object') {
         Object.values(service).forEach(mock => {
         Object.values(service).forEach(mock => {
           if (mock && typeof mock === 'function' && 'mockClear' in mock) {
           if (mock && typeof mock === 'function' && 'mockClear' in mock) {
-            mock.mockClear();
+            (mock as any).mockClear();
           }
           }
         });
         });
       }
       }

+ 2 - 3
src/server/api.ts

@@ -26,8 +26,7 @@ const api = new OpenAPIHono<AuthContext>()
 api.onError(errorHandler)
 api.onError(errorHandler)
 
 
 // Rate limiting
 // Rate limiting
-api.use('/api/v1/*', async (c, next) => {
-  const ip = c.req.header('x-forwarded-for') || c.req.header('cf-connecting-ip')
+api.use('/api/v1/*', async (_, next) => {
   // 实现速率限制逻辑
   // 实现速率限制逻辑
   await next()
   await next()
 })
 })
@@ -86,7 +85,7 @@ if(1){
   app.get('/ui', swaggerUI({
   app.get('/ui', swaggerUI({
     url: '/doc',
     url: '/doc',
     persistAuthorization: true,
     persistAuthorization: true,
-    manuallySwaggerUIHtml: (asset) => `
+    manuallySwaggerUIHtml: () => `
       <div>
       <div>
         <div id="swagger-ui"></div>
         <div id="swagger-ui"></div>
         <link rel="stylesheet" href="https://ai-oss.d8d.fun/swagger-ui-dist/swagger-ui.css" />
         <link rel="stylesheet" href="https://ai-oss.d8d.fun/swagger-ui-dist/swagger-ui.css" />

+ 1 - 1
src/server/api/auth/__tests__/auth.integration.test.ts

@@ -1,4 +1,4 @@
-import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import { describe, it, expect, beforeEach } from 'vitest';
 import { testClient } from 'hono/testing';
 import { testClient } from 'hono/testing';
 import {
 import {
   IntegrationTestDatabase,
   IntegrationTestDatabase,

+ 3 - 1
src/server/api/auth/login/password.ts

@@ -6,6 +6,7 @@ import { ErrorSchema } from '../../../utils/errorHandler'
 import { AppDataSource } from '../../../data-source'
 import { AppDataSource } from '../../../data-source'
 import { AuthContext } from '../../../types/context'
 import { AuthContext } from '../../../types/context'
 import { UserSchema } from '@/server/modules/users/user.schema'
 import { UserSchema } from '@/server/modules/users/user.schema'
+import { parseWithAwait } from '@/server/utils/parseWithAwait'
 
 
 const userService = new UserService(AppDataSource)
 const userService = new UserService(AppDataSource)
 const authService = new AuthService(userService)
 const authService = new AuthService(userService)
@@ -74,7 +75,8 @@ const app = new OpenAPIHono<AuthContext>().openapi(loginRoute, async (c) => {
   try {
   try {
     const { username, password } = c.req.valid('json')
     const { username, password } = c.req.valid('json')
     const result = await authService.login(username, password)
     const result = await authService.login(username, password)
-    return c.json(result, 200)
+    
+    return c.json(await parseWithAwait(TokenResponseSchema, result), 200)
   } catch (error) {
   } catch (error) {
     // 认证相关错误返回401
     // 认证相关错误返回401
     if (error instanceof Error &&
     if (error instanceof Error &&

+ 0 - 1
src/server/api/auth/sso-verify.ts

@@ -3,7 +3,6 @@ import { AuthService } from '@/server/modules/auth/auth.service'
 import { UserService } from '@/server/modules/users/user.service'
 import { UserService } from '@/server/modules/users/user.service'
 import { ErrorSchema } from '@/server/utils/errorHandler'
 import { ErrorSchema } from '@/server/utils/errorHandler'
 import { AppDataSource } from '@/server/data-source'
 import { AppDataSource } from '@/server/data-source'
-import { AuthContext } from '@/server/types/context'
 
 
 const userService = new UserService(AppDataSource)
 const userService = new UserService(AppDataSource)
 const authService = new AuthService(userService)
 const authService = new AuthService(userService)

+ 0 - 1
src/server/api/files/[id]/get-url.ts

@@ -1,6 +1,5 @@
 import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
 import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
 import { FileService } from '@/server/modules/files/file.service';
 import { FileService } from '@/server/modules/files/file.service';
-import { MinioService } from '@/server/modules/files/minio.service';
 import { ErrorSchema } from '@/server/utils/errorHandler';
 import { ErrorSchema } from '@/server/utils/errorHandler';
 import { AppDataSource } from '@/server/data-source';
 import { AppDataSource } from '@/server/data-source';
 import { AuthContext } from '@/server/types/context';
 import { AuthContext } from '@/server/types/context';

+ 6 - 2
src/server/api/files/multipart-complete/post.ts

@@ -41,9 +41,9 @@ const CompleteMultipartUploadDto = z.object({
 
 
 // 完成分片上传响应Schema
 // 完成分片上传响应Schema
 const CompleteMultipartUploadResponse = z.object({
 const CompleteMultipartUploadResponse = z.object({
-  fileId: z.string().openapi({
+  fileId: z.number().openapi({
     description: '文件ID',
     description: '文件ID',
-    example: 'file_123456'
+    example: 123456
   }),
   }),
   url: z.string().openapi({
   url: z.string().openapi({
     description: '文件访问URL',
     description: '文件访问URL',
@@ -60,6 +60,10 @@ const CompleteMultipartUploadResponse = z.object({
   key: z.string().openapi({
   key: z.string().openapi({
     description: '文件键名',
     description: '文件键名',
     example: 'documents/report.pdf'
     example: 'documents/report.pdf'
+  }),
+  size: z.number().openapi({
+    description: '文件大小(字节)',
+    example: 102400
   })
   })
 });
 });
 
 

+ 0 - 1
src/server/api/files/multipart-policy/post.ts

@@ -1,6 +1,5 @@
 import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
 import { createRoute, OpenAPIHono, z } from '@hono/zod-openapi';
 import { FileService } from '@/server/modules/files/file.service';
 import { FileService } from '@/server/modules/files/file.service';
-import { MinioService } from '@/server/modules/files/minio.service';
 import { ErrorSchema } from '@/server/utils/errorHandler';
 import { ErrorSchema } from '@/server/utils/errorHandler';
 import { AppDataSource } from '@/server/data-source';
 import { AppDataSource } from '@/server/data-source';
 import { AuthContext } from '@/server/types/context';
 import { AuthContext } from '@/server/types/context';

+ 0 - 1
src/server/api/roles/index.ts

@@ -2,7 +2,6 @@ import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
 import { Role } from '@/server/modules/users/role.entity';
 import { Role } from '@/server/modules/users/role.entity';
 import { RoleSchema, CreateRoleDto, UpdateRoleDto } from '@/server/modules/users/role.schema';
 import { RoleSchema, CreateRoleDto, UpdateRoleDto } from '@/server/modules/users/role.schema';
 import { authMiddleware } from '@/server/middleware/auth.middleware';
 import { authMiddleware } from '@/server/middleware/auth.middleware';
-import { checkPermission, permissionMiddleware } from '@/server/middleware/permission.middleware';
 import { OpenAPIHono } from '@hono/zod-openapi';
 import { OpenAPIHono } from '@hono/zod-openapi';
 
 
 // 创建角色CRUD路由
 // 创建角色CRUD路由

+ 2 - 2
src/server/api/users/__tests__/users.integration.test.ts

@@ -1,4 +1,4 @@
-import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import { describe, it, expect, beforeEach } from 'vitest';
 import { testClient } from 'hono/testing';
 import { testClient } from 'hono/testing';
 import {
 import {
   IntegrationTestDatabase,
   IntegrationTestDatabase,
@@ -75,7 +75,7 @@ describe('用户API集成测试 (使用hono/testing)', () => {
       if (!dataSource) throw new Error('Database not initialized');
       if (!dataSource) throw new Error('Database not initialized');
 
 
       // 先创建一个用户
       // 先创建一个用户
-      const existingUser = await TestDataFactory.createTestUser(dataSource, {
+      await TestDataFactory.createTestUser(dataSource, {
         username: 'duplicate_user'
         username: 'duplicate_user'
       });
       });
 
 

+ 3 - 2
src/server/api/users/custom.ts

@@ -6,6 +6,7 @@ import { ErrorSchema } from '../../utils/errorHandler';
 import { AppDataSource } from '../../data-source';
 import { AppDataSource } from '../../data-source';
 import { AuthContext } from '../../types/context';
 import { AuthContext } from '../../types/context';
 import { CreateUserDto, UpdateUserDto, UserSchema } from '../../modules/users/user.schema';
 import { CreateUserDto, UpdateUserDto, UserSchema } from '../../modules/users/user.schema';
+import { parseWithAwait } from '../../utils/parseWithAwait';
 
 
 // 创建用户路由 - 自定义业务逻辑(密码加密等)
 // 创建用户路由 - 自定义业务逻辑(密码加密等)
 const createUserRoute = createRoute({
 const createUserRoute = createRoute({
@@ -112,7 +113,7 @@ const app = new OpenAPIHono<AuthContext>()
       const userService = new UserService(AppDataSource);
       const userService = new UserService(AppDataSource);
       const result = await userService.createUser(data);
       const result = await userService.createUser(data);
 
 
-      return c.json(result, 201);
+      return c.json(await parseWithAwait(UserSchema, result), 201);
     } catch (error) {
     } catch (error) {
       if (error instanceof z.ZodError) {
       if (error instanceof z.ZodError) {
         return c.json({
         return c.json({
@@ -138,7 +139,7 @@ const app = new OpenAPIHono<AuthContext>()
         return c.json({ code: 404, message: '资源不存在' }, 404);
         return c.json({ code: 404, message: '资源不存在' }, 404);
       }
       }
 
 
-      return c.json(result, 200);
+      return c.json(await parseWithAwait(UserSchema, result), 200);
     } catch (error) {
     } catch (error) {
       if (error instanceof z.ZodError) {
       if (error instanceof z.ZodError) {
         return c.json({
         return c.json({

+ 2 - 2
src/server/modules/files/__tests__/minio.service.test.ts

@@ -303,7 +303,7 @@ describe('MinioService', () => {
       ];
       ];
       const mockStat = { size: 2048 };
       const mockStat = { size: 2048 };
 
 
-      vi.mocked(mockClient.completeMultipartUpload).mockResolvedValue(undefined);
+      vi.mocked(mockClient.completeMultipartUpload).mockResolvedValue({ etag: 'etag123', versionId: null });
       vi.mocked(mockClient.statObject).mockResolvedValue(mockStat as any);
       vi.mocked(mockClient.statObject).mockResolvedValue(mockStat as any);
 
 
       const result = await minioService.completeMultipartUpload(
       const result = await minioService.completeMultipartUpload(
@@ -349,7 +349,7 @@ describe('MinioService', () => {
       const mockUrl = 'http://localhost:9000/test-bucket/file.txt';
       const mockUrl = 'http://localhost:9000/test-bucket/file.txt';
 
 
       vi.spyOn(minioService, 'ensureBucketExists').mockResolvedValue(true);
       vi.spyOn(minioService, 'ensureBucketExists').mockResolvedValue(true);
-      vi.mocked(mockClient.putObject).mockResolvedValue(undefined);
+      vi.mocked(mockClient.putObject).mockResolvedValue({ etag: 'etag123', versionId: null });
       vi.spyOn(minioService, 'getFileUrl').mockReturnValue(mockUrl);
       vi.spyOn(minioService, 'getFileUrl').mockReturnValue(mockUrl);
 
 
       const result = await minioService.createObject(
       const result = await minioService.createObject(

+ 1 - 1
src/server/modules/users/role.service.ts

@@ -1,4 +1,4 @@
-import { DataSource, Repository } from 'typeorm';
+import { DataSource } from 'typeorm';
 import { Role } from './role.entity';
 import { Role } from './role.entity';
 import { GenericCrudService } from '@/server/utils/generic-crud.service';
 import { GenericCrudService } from '@/server/utils/generic-crud.service';
 
 

+ 0 - 1
src/server/renderer.tsx

@@ -1,5 +1,4 @@
 import { GlobalConfig } from '@/share/types'
 import { GlobalConfig } from '@/share/types'
-import { reactRenderer } from '@hono/react-renderer'
 // import { Script, Link } from 'hono-vite-react-stack-node/components'
 // import { Script, Link } from 'hono-vite-react-stack-node/components'
 import process from 'node:process'
 import process from 'node:process'
 
 

+ 0 - 2
src/server/utils/__tests__/backup.test.ts

@@ -1,6 +1,5 @@
 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
 import { DatabaseBackup } from '../backup'
 import { DatabaseBackup } from '../backup'
-import { promises as fs } from 'fs'
 import path from 'path'
 import path from 'path'
 
 
 // Mock pg-dump-restore
 // Mock pg-dump-restore
@@ -179,7 +178,6 @@ describe('DatabaseBackup', () => {
 
 
   describe('startScheduledBackups', () => {
   describe('startScheduledBackups', () => {
     it('应该启动定时备份任务', async () => {
     it('应该启动定时备份任务', async () => {
-      const cron = await import('node-cron')
       const { logger } = await import('../logger')
       const { logger } = await import('../logger')
 
 
       backup.startScheduledBackups()
       backup.startScheduledBackups()

+ 0 - 1
src/server/utils/__tests__/restore.test.ts

@@ -1,6 +1,5 @@
 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
 import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
 import { DatabaseRestore } from '../restore'
 import { DatabaseRestore } from '../restore'
-import { promises as fs } from 'fs'
 import path from 'path'
 import path from 'path'
 
 
 // Mock pg-dump-restore
 // Mock pg-dump-restore

+ 4 - 0
src/server/utils/generic-crud.routes.ts

@@ -267,6 +267,7 @@ export function createCrudRoutes<
           }, 500);
           }, 500);
         }
         }
       })
       })
+      // @ts-ignore
       .openapi(createRouteDef, async (c: any) => {
       .openapi(createRouteDef, async (c: any) => {
         try {
         try {
           const data = c.req.valid('json');
           const data = c.req.valid('json');
@@ -288,6 +289,7 @@ export function createCrudRoutes<
           }, 500);
           }, 500);
         }
         }
       })
       })
+      // @ts-ignore
       .openapi(getRouteDef, async (c: any) => {
       .openapi(getRouteDef, async (c: any) => {
         try {
         try {
           const { id } = c.req.valid('param');
           const { id } = c.req.valid('param');
@@ -314,6 +316,7 @@ export function createCrudRoutes<
           }, 500);
           }, 500);
         }
         }
       })
       })
+      // @ts-ignore
       .openapi(updateRouteDef, async (c: any) => {
       .openapi(updateRouteDef, async (c: any) => {
         try {
         try {
           const { id } = c.req.valid('param');
           const { id } = c.req.valid('param');
@@ -423,6 +426,7 @@ export function createCrudRoutes<
           }, 500);
           }, 500);
         }
         }
       })
       })
+      // @ts-ignore
       .openapi(getRouteDef, async (c: any) => {
       .openapi(getRouteDef, async (c: any) => {
         try {
         try {
           const { id } = c.req.valid('param');
           const { id } = c.req.valid('param');

+ 1 - 1
src/server/utils/generic-crud.service.ts

@@ -198,7 +198,7 @@ export abstract class GenericCrudService<T extends ObjectLiteral> {
   /**
   /**
    * 处理关联字段
    * 处理关联字段
    */
    */
-  private async handleRelationFields(data: any, entity: T, isUpdate: boolean = false): Promise<void> {
+  private async handleRelationFields(data: any, entity: T, _isUpdate: boolean = false): Promise<void> {
     if (!this.relationFields) return;
     if (!this.relationFields) return;
 
 
     for (const [fieldName, config] of Object.entries(this.relationFields)) {
     for (const [fieldName, config] of Object.entries(this.relationFields)) {

+ 1 - 1
src/server/utils/parseWithAwait.ts

@@ -26,7 +26,7 @@ export async function parseWithAwait<T>(schema: z.ZodSchema<T>, data: unknown):
   }  
   }  
     
     
   async function resolvePromisesByPath(data: any, promiseErrors: any[]): Promise<any> {  
   async function resolvePromisesByPath(data: any, promiseErrors: any[]): Promise<any> {  
-    const clonedData = JSON.parse(JSON.stringify(data, (key, value) => {  
+    const clonedData = JSON.parse(JSON.stringify(data, (_, value) => {  
       // 保留 Promise 对象,不进行序列化  
       // 保留 Promise 对象,不进行序列化  
       return typeof value?.then === 'function' ? value : value;  
       return typeof value?.then === 'function' ? value : value;  
     }));  
     }));  

+ 1 - 0
src/test/test-utils.ts

@@ -1,5 +1,6 @@
 import { OpenAPIHono } from '@hono/zod-openapi';
 import { OpenAPIHono } from '@hono/zod-openapi';
 import { Hono } from 'hono';
 import { Hono } from 'hono';
+import { vi } from 'vitest';
 
 
 /**
 /**
  * 创建测试服务器实例
  * 创建测试服务器实例