소스 검색

✨ feat(vocabulary): 添加单词发音音频文件关联功能

- 在词汇实体中添加pronunciationFileId字段和pronunciationFile关联关系
- 更新词汇schema,添加发音文件相关字段定义
- 在词汇CRUD路由中添加relations配置,支持查询发音文件信息
- 添加创建和更新词汇时的发音文件ID参数支持
yourname 3 달 전
부모
커밋
c6941bc9b5
3개의 변경된 파일33개의 추가작업 그리고 2개의 파일을 삭제
  1. 1 0
      src/server/api/vocabularies/index.ts
  2. 9 1
      src/server/modules/vocabulary/vocabulary.entity.ts
  3. 23 1
      src/server/modules/vocabulary/vocabulary.schema.ts

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

@@ -10,6 +10,7 @@ const vocabularyRoutes = createCrudRoutes({
   getSchema: VocabularySchema,
   listSchema: VocabularySchema,
   searchFields: ['word', 'pronunciation', 'meaning'],
+  relations: ['pronunciationFile'], // 关联查询发音音频文件信息
   middleware: [authMiddleware]
 });
 

+ 9 - 1
src/server/modules/vocabulary/vocabulary.entity.ts

@@ -1,5 +1,6 @@
-import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
 import { DeleteStatus, DisabledStatus } from '@/share/types';
+import { File } from '@/server/modules/files/file.entity';
 
 @Entity({ name: 'vocabularies' })
 export class VocabularyEntity {
@@ -30,6 +31,13 @@ export class VocabularyEntity {
   @UpdateDateColumn({ name: 'updated_at', type: 'timestamp' })
   updatedAt!: Date;
 
+  @Column({ name: 'pronunciation_file_id', type: 'int', unsigned: true, nullable: true, comment: '发音音频文件ID' })
+  pronunciationFileId!: number | null;
+
+  @ManyToOne(() => File, { nullable: true })
+  @JoinColumn({ name: 'pronunciation_file_id', referencedColumnName: 'id' })
+  pronunciationFile!: File | null;
+
   constructor(partial?: Partial<VocabularyEntity>) {
     Object.assign(this, partial);
   }

+ 23 - 1
src/server/modules/vocabulary/vocabulary.schema.ts

@@ -1,5 +1,6 @@
 import { z } from '@hono/zod-openapi';
 import { DeleteStatus, DisabledStatus } from '@/share/types';
+import { FileSchema } from '@/server/modules/files/file.schema';
 
 // 基础单词 schema(包含所有字段)
 export const VocabularySchema = z.object({
@@ -29,7 +30,20 @@ export const VocabularySchema = z.object({
     description: '是否删除(0:未删除,1:已删除)'
   }),
   createdAt: z.coerce.date().openapi({ description: '创建时间' }),
-  updatedAt: z.coerce.date().openapi({ description: '更新时间' })
+  updatedAt: z.coerce.date().openapi({ description: '更新时间' }),
+  pronunciationFileId: z.number().int().positive().nullable().openapi({
+    example: 1,
+    description: '发音音频文件ID'
+  }),
+  pronunciationFile: z.object({
+    id: z.number().int().positive().openapi({ description: '文件ID' }),
+    name: z.string().max(255).openapi({ description: '文件名', example: 'hello.mp3' }),
+    fullUrl: z.string().openapi({ description: '文件完整URL', example: 'https://example.com/hello.mp3' }),
+    type: z.string().nullable().openapi({ description: '文件类型', example: 'audio/mpeg' }),
+    size: z.number().nullable().openapi({ description: '文件大小(字节)', example: 102400 })
+  }).nullable().optional().openapi({
+    description: '发音音频文件信息'
+  })
 });
 
 // 创建单词请求 schema
@@ -53,6 +67,10 @@ export const CreateVocabularyDto = z.object({
   isDisabled: z.number().int().min(0, '状态值只能是0或1').max(1, '状态值只能是0或1').default(DisabledStatus.ENABLED).optional().openapi({
     example: DisabledStatus.ENABLED,
     description: '是否禁用(0:启用,1:禁用)'
+  }),
+  pronunciationFileId: z.number().int().positive().nullable().optional().openapi({
+    example: 1,
+    description: '发音音频文件ID'
   })
 });
 
@@ -77,6 +95,10 @@ export const UpdateVocabularyDto = z.object({
   isDisabled: z.number().int().min(0, '状态值只能是0或1').max(1, '状态值只能是0或1').optional().openapi({
     example: DisabledStatus.ENABLED,
     description: '是否禁用(0:启用,1:禁用)'
+  }),
+  pronunciationFileId: z.number().int().positive().nullable().optional().openapi({
+    example: 1,
+    description: '发音音频文件ID'
   })
 });