import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn, OneToMany } from 'typeorm'; import { UserEntity } from '../users/user.entity'; import { SilverKnowledgeCategory } from './silver-knowledge-category.entity'; import { SilverKnowledgeInteraction } from './silver-knowledge-interaction.entity'; import { SilverKnowledgeTagRelation } from './silver-knowledge-tag-relation.entity'; import { z } from '@hono/zod-openapi'; export enum KnowledgeType { ARTICLE = 1, // 文章 VIDEO = 2, // 视频 DOCUMENT = 3, // 文档 COURSE = 4, // 课程 EXPERIENCE = 5, // 经验分享 CASE = 6, // 案例分享 RESEARCH = 7 // 研究报告 } export enum KnowledgeStatus { DRAFT = 0, // 草稿 PUBLISHED = 1, // 已发布 HIDDEN = 2, // 已隐藏 DELETED = 3, // 已删除 REVIEWING = 4 // 审核中 } @Entity('silver_knowledges') export class SilverKnowledge { @PrimaryGeneratedColumn({ unsigned: true }) id!: number; @Column({ name: 'user_id', type: 'int', unsigned: true }) userId!: number; @ManyToOne(() => UserEntity, user => user.silverKnowledges) @JoinColumn({ name: 'user_id' }) user!: UserEntity; @Column({ name: 'category_id', type: 'int', unsigned: true, nullable: true }) categoryId!: number | null; @ManyToOne(() => SilverKnowledgeCategory, category => category.silverKnowledges) @JoinColumn({ name: 'category_id' }) category!: SilverKnowledgeCategory | null; @Column({ name: 'title', type: 'varchar', length: 255 }) title!: string; @Column({ name: 'content', type: 'text' }) content!: string; @Column({ name: 'summary', type: 'text', nullable: true }) summary!: string | null; @Column({ name: 'type', type: 'tinyint', unsigned: true }) type!: KnowledgeType; @Column({ name: 'tags', type: 'text', nullable: true }) tags!: string | null; // JSON array of tags @Column({ name: 'cover_image', type: 'varchar', length: 500, nullable: true }) coverImage!: string | null; @Column({ name: 'attachments', type: 'text', nullable: true }) attachments!: string | null; // JSON array of attachment URLs @Column({ name: 'status', type: 'tinyint', default: KnowledgeStatus.DRAFT }) status!: KnowledgeStatus; // 统计字段 - 单个知识统计 @Column({ name: 'view_count', type: 'int', unsigned: true, default: 0 }) viewCount!: number; @Column({ name: 'download_count', type: 'int', unsigned: true, default: 0 }) downloadCount!: number; @Column({ name: 'like_count', type: 'int', unsigned: true, default: 0 }) likeCount!: number; @Column({ name: 'comment_count', type: 'int', unsigned: true, default: 0 }) commentCount!: number; @Column({ name: 'favorite_count', type: 'int', unsigned: true, default: 0 }) favoriteCount!: number; @Column({ name: 'share_count', type: 'int', unsigned: true, default: 0 }) shareCount!: number; @Column({ name: 'read_count', type: 'int', unsigned: true, default: 0 }) readCount!: number; // 更准确的阅读统计 // 推荐和排序 @Column({ name: 'is_featured', type: 'tinyint', default: 0 }) isFeatured!: number; @Column({ name: 'featured_at', type: 'timestamp', nullable: true }) featuredAt!: Date | null; @Column({ name: 'sort_order', type: 'int', default: 0 }) sortOrder!: number; // 元数据 @Column({ name: 'keywords', type: 'text', nullable: true }) keywords!: string | null; // 用于搜索优化的关键词 @Column({ name: 'source', type: 'varchar', length: 255, nullable: true }) source!: string | null; // 知识来源 @Column({ name: 'author', type: 'varchar', length: 100, nullable: true }) author!: string | null; // 原作者 @Column({ name: 'duration', type: 'int', unsigned: true, nullable: true }) duration!: number | null; // 视频时长(秒)或阅读时长(分钟) // 时间戳 @CreateDateColumn({ name: 'created_at' }) createdAt!: Date; @UpdateDateColumn({ name: 'updated_at' }) updatedAt!: Date; @Column({ name: 'published_at', type: 'timestamp', nullable: true }) publishedAt!: Date | null; // 用户跟踪 @Column({ name: 'created_by', type: 'int', nullable: true }) createdBy!: number | null; @Column({ name: 'updated_by', type: 'int', nullable: true }) updatedBy!: number | null; @OneToMany(() => SilverKnowledgeInteraction, interaction => interaction.knowledge) silverKnowledgeInteractions!: SilverKnowledgeInteraction[]; @OneToMany(() => SilverKnowledgeTagRelation, tagRelation => tagRelation.knowledge) silverKnowledgeTagRelations!: SilverKnowledgeTagRelation[]; } // Zod Schema定义 export const SilverKnowledgeSchema = z.object({ id: z.number().int().positive().openapi({ description: '知识ID', example: 1 }), userId: z.number().int().positive().openapi({ description: '发布用户ID', example: 1 }), user: UserSchema.optional().openapi({ description: '发布用户', example: { id: 1, username: 'user123', nickname: '张三' } }), categoryId: z.number().int().positive().optional().nullable().openapi({ description: '分类ID', example: 5 }), category: SilverKnowledgeCategorySchema.optional().nullable().openapi({ description: '知识分类', example: { id: 5, name: '健康养生' } }), title: z.string().max(255).openapi({ description: '知识标题', example: '老年人健康饮食指南' }), content: z.string().openapi({ description: '知识内容', example: '老年人健康饮食需要注意以下几点...' }), summary: z.string().optional().openapi({ description: '知识摘要', example: '本指南详细介绍了老年人健康饮食的科学方法...' }), type: z.coerce.number().int().min(1).max(7).openapi({ description: '知识类型 1:文章 2:视频 3:文档 4:课程 5:经验分享 6:案例分享 7:研究报告', example: 1 }), tags: z.string().optional().nullable().openapi({ description: '标签(JSON数组)', example: '["健康养生", "老年人", "饮食"]' }), coverImage: z.string().max(500).url().optional().nullable().openapi({ description: '封面图片URL', example: 'https://example.com/cover.jpg' }), attachments: z.string().optional().nullable().openapi({ description: '附件URL(JSON数组)', example: '["https://example.com/attach1.pdf"]' }), status: z.coerce.number().int().min(0).max(4).default(0).openapi({ description: '状态 0:草稿 1:已发布 2:已隐藏 3:已删除 4:审核中', example: 1 }), viewCount: z.number().int().min(0).default(0).openapi({ description: '浏览次数', example: 1234 }), downloadCount: z.number().int().min(0).default(0).openapi({ description: '下载次数', example: 567 }), likeCount: z.number().int().min(0).default(0).openapi({ description: '点赞次数', example: 234 }), commentCount: z.number().int().min(0).default(0).openapi({ description: '评论次数', example: 89 }), favoriteCount: z.number().int().min(0).default(0).openapi({ description: '收藏次数', example: 156 }), shareCount: z.number().int().min(0).default(0).openapi({ description: '分享次数', example: 78 }), readCount: z.number().int().min(0).default(0).openapi({ description: '阅读次数', example: 2345 }), isFeatured: z.coerce.number().int().min(0).max(1).default(0).openapi({ description: '是否推荐 0:否 1:是', example: 1 }), featuredAt: z.date().optional().nullable().openapi({ description: '推荐时间', example: '2024-01-01T00:00:00Z' }), sortOrder: z.number().int().default(0).openapi({ description: '排序顺序', example: 1 }), keywords: z.string().optional().nullable().openapi({ description: '搜索关键词', example: '健康,养生,老年人' }), source: z.string().max(255).optional().nullable().openapi({ description: '知识来源', example: '中国老年学会' }), author: z.string().max(100).optional().nullable().openapi({ description: '原作者', example: '张医生' }), duration: z.number().int().positive().optional().nullable().openapi({ description: '时长(秒/分钟)', example: 15 }), createdAt: z.date().openapi({ description: '创建时间', example: '2024-01-01T00:00:00Z' }), updatedAt: z.date().openapi({ description: '更新时间', example: '2024-01-01T00:00:00Z' }), publishedAt: z.date().optional().nullable().openapi({ description: '发布时间', example: '2024-01-01T00:00:00Z' }) }); export const CreateSilverKnowledgeDto = z.object({ userId: z.coerce.number().int().positive().openapi({ description: '发布用户ID', example: 1 }), categoryId: z.number().int().positive().optional().openapi({ description: '分类ID', example: 5 }), title: z.string().max(255).openapi({ description: '知识标题', example: '老年人健康饮食指南' }), content: z.string().openapi({ description: '知识内容', example: '老年人健康饮食需要注意以下几点...' }), summary: z.string().optional().openapi({ description: '知识摘要', example: '本指南详细介绍了老年人健康饮食的科学方法...' }), type: z.coerce.number().int().min(1).max(7).openapi({ description: '知识类型', example: 1 }), tags: z.string().optional().openapi({ description: '标签(JSON数组)', example: '["健康养生", "老年人", "饮食"]' }), coverImage: z.string().max(500).url().optional().openapi({ description: '封面图片URL', example: 'https://example.com/cover.jpg' }), attachments: z.string().optional().openapi({ description: '附件URL(JSON数组)', example: '["https://example.com/attach1.pdf"]' }), keywords: z.string().optional().openapi({ description: '搜索关键词', example: '健康,养生,老年人' }), source: z.string().max(255).optional().openapi({ description: '知识来源', example: '中国老年学会' }), author: z.string().max(100).optional().openapi({ description: '原作者', example: '张医生' }), duration: z.number().int().positive().optional().openapi({ description: '时长(秒/分钟)', example: 15 }) }); export const UpdateSilverKnowledgeDto = z.object({ categoryId: z.number().int().positive().optional().openapi({ description: '分类ID', example: 5 }), title: z.string().max(255).optional().openapi({ description: '知识标题', example: '老年人健康饮食指南' }), content: z.string().optional().openapi({ description: '知识内容', example: '老年人健康饮食需要注意以下几点...' }), summary: z.string().optional().openapi({ description: '知识摘要', example: '本指南详细介绍了老年人健康饮食的科学方法...' }), type: z.coerce.number().int().min(1).max(7).optional().openapi({ description: '知识类型', example: 1 }), tags: z.string().optional().openapi({ description: '标签(JSON数组)', example: '["健康养生", "老年人", "饮食"]' }), coverImage: z.string().max(500).url().optional().openapi({ description: '封面图片URL', example: 'https://example.com/cover.jpg' }), attachments: z.string().optional().openapi({ description: '附件URL(JSON数组)', example: '["https://example.com/attach1.pdf"]' }), status: z.coerce.number().int().min(0).max(4).optional().openapi({ description: '状态', example: 1 }), keywords: z.string().optional().openapi({ description: '搜索关键词', example: '健康,养生,老年人' }), source: z.string().max(255).optional().openapi({ description: '知识来源', example: '中国老年学会' }), author: z.string().max(100).optional().openapi({ description: '原作者', example: '张医生' }), duration: z.number().int().positive().optional().openapi({ description: '时长(秒/分钟)', example: 15 }), isFeatured: z.coerce.number().int().min(0).max(1).optional().openapi({ description: '是否推荐', example: 1 }), featuredAt: z.date().optional().nullable().openapi({ description: '推荐时间', example: '2024-01-01T00:00:00Z' }), sortOrder: z.number().int().optional().openapi({ description: '排序顺序', example: 1 }) });