|
|
@@ -0,0 +1,356 @@
|
|
|
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
|
|
+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;
|
|
|
+
|
|
|
+ @Column({ name: 'category_id', type: 'int', unsigned: true, nullable: true })
|
|
|
+ categoryId!: number | 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;
|
|
|
+}
|
|
|
+
|
|
|
+// 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
|
|
|
+ }),
|
|
|
+ categoryId: z.number().int().positive().optional().nullable().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: '知识类型 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
|
|
|
+ })
|
|
|
+});
|