silver-knowledge.entity.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn, OneToMany } from 'typeorm';
  2. import { UserEntity } from '../users/user.entity';
  3. import { SilverKnowledgeCategory } from './silver-knowledge-category.entity';
  4. import { SilverKnowledgeInteraction } from './silver-knowledge-interaction.entity';
  5. import { SilverKnowledgeTagRelation } from './silver-knowledge-tag-relation.entity';
  6. import { z } from '@hono/zod-openapi';
  7. export enum KnowledgeType {
  8. ARTICLE = 1, // 文章
  9. VIDEO = 2, // 视频
  10. DOCUMENT = 3, // 文档
  11. COURSE = 4, // 课程
  12. EXPERIENCE = 5, // 经验分享
  13. CASE = 6, // 案例分享
  14. RESEARCH = 7 // 研究报告
  15. }
  16. export enum KnowledgeStatus {
  17. DRAFT = 0, // 草稿
  18. PUBLISHED = 1, // 已发布
  19. HIDDEN = 2, // 已隐藏
  20. DELETED = 3, // 已删除
  21. REVIEWING = 4 // 审核中
  22. }
  23. @Entity('silver_knowledges')
  24. export class SilverKnowledge {
  25. @PrimaryGeneratedColumn({ unsigned: true })
  26. id!: number;
  27. @Column({ name: 'user_id', type: 'int', unsigned: true })
  28. userId!: number;
  29. @ManyToOne(() => UserEntity, user => user.silverKnowledges)
  30. @JoinColumn({ name: 'user_id' })
  31. user!: UserEntity;
  32. @Column({ name: 'category_id', type: 'int', unsigned: true, nullable: true })
  33. categoryId!: number | null;
  34. @ManyToOne(() => SilverKnowledgeCategory, category => category.silverKnowledges)
  35. @JoinColumn({ name: 'category_id' })
  36. category!: SilverKnowledgeCategory | null;
  37. @Column({ name: 'title', type: 'varchar', length: 255 })
  38. title!: string;
  39. @Column({ name: 'content', type: 'text' })
  40. content!: string;
  41. @Column({ name: 'summary', type: 'text', nullable: true })
  42. summary!: string | null;
  43. @Column({ name: 'type', type: 'tinyint', unsigned: true })
  44. type!: KnowledgeType;
  45. @Column({ name: 'tags', type: 'text', nullable: true })
  46. tags!: string | null; // JSON array of tags
  47. @Column({ name: 'cover_image', type: 'varchar', length: 500, nullable: true })
  48. coverImage!: string | null;
  49. @Column({ name: 'attachments', type: 'text', nullable: true })
  50. attachments!: string | null; // JSON array of attachment URLs
  51. @Column({ name: 'status', type: 'tinyint', default: KnowledgeStatus.DRAFT })
  52. status!: KnowledgeStatus;
  53. // 统计字段 - 单个知识统计
  54. @Column({ name: 'view_count', type: 'int', unsigned: true, default: 0 })
  55. viewCount!: number;
  56. @Column({ name: 'download_count', type: 'int', unsigned: true, default: 0 })
  57. downloadCount!: number;
  58. @Column({ name: 'like_count', type: 'int', unsigned: true, default: 0 })
  59. likeCount!: number;
  60. @Column({ name: 'comment_count', type: 'int', unsigned: true, default: 0 })
  61. commentCount!: number;
  62. @Column({ name: 'favorite_count', type: 'int', unsigned: true, default: 0 })
  63. favoriteCount!: number;
  64. @Column({ name: 'share_count', type: 'int', unsigned: true, default: 0 })
  65. shareCount!: number;
  66. @Column({ name: 'read_count', type: 'int', unsigned: true, default: 0 })
  67. readCount!: number; // 更准确的阅读统计
  68. // 推荐和排序
  69. @Column({ name: 'is_featured', type: 'tinyint', default: 0 })
  70. isFeatured!: number;
  71. @Column({ name: 'featured_at', type: 'timestamp', nullable: true })
  72. featuredAt!: Date | null;
  73. @Column({ name: 'sort_order', type: 'int', default: 0 })
  74. sortOrder!: number;
  75. // 元数据
  76. @Column({ name: 'keywords', type: 'text', nullable: true })
  77. keywords!: string | null; // 用于搜索优化的关键词
  78. @Column({ name: 'source', type: 'varchar', length: 255, nullable: true })
  79. source!: string | null; // 知识来源
  80. @Column({ name: 'author', type: 'varchar', length: 100, nullable: true })
  81. author!: string | null; // 原作者
  82. @Column({ name: 'duration', type: 'int', unsigned: true, nullable: true })
  83. duration!: number | null; // 视频时长(秒)或阅读时长(分钟)
  84. // 时间戳
  85. @CreateDateColumn({ name: 'created_at' })
  86. createdAt!: Date;
  87. @UpdateDateColumn({ name: 'updated_at' })
  88. updatedAt!: Date;
  89. @Column({ name: 'published_at', type: 'timestamp', nullable: true })
  90. publishedAt!: Date | null;
  91. // 用户跟踪
  92. @Column({ name: 'created_by', type: 'int', nullable: true })
  93. createdBy!: number | null;
  94. @Column({ name: 'updated_by', type: 'int', nullable: true })
  95. updatedBy!: number | null;
  96. @OneToMany(() => SilverKnowledgeInteraction, interaction => interaction.knowledge)
  97. silverKnowledgeInteractions!: SilverKnowledgeInteraction[];
  98. @OneToMany(() => SilverKnowledgeTagRelation, tagRelation => tagRelation.knowledge)
  99. silverKnowledgeTagRelations!: SilverKnowledgeTagRelation[];
  100. }
  101. // Zod Schema定义
  102. export const SilverKnowledgeSchema = z.object({
  103. id: z.number().int().positive().openapi({
  104. description: '知识ID',
  105. example: 1
  106. }),
  107. userId: z.number().int().positive().openapi({
  108. description: '发布用户ID',
  109. example: 1
  110. }),
  111. user: UserSchema.optional().openapi({
  112. description: '发布用户',
  113. example: {
  114. id: 1,
  115. username: 'user123',
  116. nickname: '张三'
  117. }
  118. }),
  119. categoryId: z.number().int().positive().optional().nullable().openapi({
  120. description: '分类ID',
  121. example: 5
  122. }),
  123. category: SilverKnowledgeCategorySchema.optional().nullable().openapi({
  124. description: '知识分类',
  125. example: {
  126. id: 5,
  127. name: '健康养生'
  128. }
  129. }),
  130. title: z.string().max(255).openapi({
  131. description: '知识标题',
  132. example: '老年人健康饮食指南'
  133. }),
  134. content: z.string().openapi({
  135. description: '知识内容',
  136. example: '老年人健康饮食需要注意以下几点...'
  137. }),
  138. summary: z.string().optional().openapi({
  139. description: '知识摘要',
  140. example: '本指南详细介绍了老年人健康饮食的科学方法...'
  141. }),
  142. type: z.coerce.number().int().min(1).max(7).openapi({
  143. description: '知识类型 1:文章 2:视频 3:文档 4:课程 5:经验分享 6:案例分享 7:研究报告',
  144. example: 1
  145. }),
  146. tags: z.string().optional().nullable().openapi({
  147. description: '标签(JSON数组)',
  148. example: '["健康养生", "老年人", "饮食"]'
  149. }),
  150. coverImage: z.string().max(500).url().optional().nullable().openapi({
  151. description: '封面图片URL',
  152. example: 'https://example.com/cover.jpg'
  153. }),
  154. attachments: z.string().optional().nullable().openapi({
  155. description: '附件URL(JSON数组)',
  156. example: '["https://example.com/attach1.pdf"]'
  157. }),
  158. status: z.coerce.number().int().min(0).max(4).default(0).openapi({
  159. description: '状态 0:草稿 1:已发布 2:已隐藏 3:已删除 4:审核中',
  160. example: 1
  161. }),
  162. viewCount: z.number().int().min(0).default(0).openapi({
  163. description: '浏览次数',
  164. example: 1234
  165. }),
  166. downloadCount: z.number().int().min(0).default(0).openapi({
  167. description: '下载次数',
  168. example: 567
  169. }),
  170. likeCount: z.number().int().min(0).default(0).openapi({
  171. description: '点赞次数',
  172. example: 234
  173. }),
  174. commentCount: z.number().int().min(0).default(0).openapi({
  175. description: '评论次数',
  176. example: 89
  177. }),
  178. favoriteCount: z.number().int().min(0).default(0).openapi({
  179. description: '收藏次数',
  180. example: 156
  181. }),
  182. shareCount: z.number().int().min(0).default(0).openapi({
  183. description: '分享次数',
  184. example: 78
  185. }),
  186. readCount: z.number().int().min(0).default(0).openapi({
  187. description: '阅读次数',
  188. example: 2345
  189. }),
  190. isFeatured: z.coerce.number().int().min(0).max(1).default(0).openapi({
  191. description: '是否推荐 0:否 1:是',
  192. example: 1
  193. }),
  194. featuredAt: z.date().optional().nullable().openapi({
  195. description: '推荐时间',
  196. example: '2024-01-01T00:00:00Z'
  197. }),
  198. sortOrder: z.number().int().default(0).openapi({
  199. description: '排序顺序',
  200. example: 1
  201. }),
  202. keywords: z.string().optional().nullable().openapi({
  203. description: '搜索关键词',
  204. example: '健康,养生,老年人'
  205. }),
  206. source: z.string().max(255).optional().nullable().openapi({
  207. description: '知识来源',
  208. example: '中国老年学会'
  209. }),
  210. author: z.string().max(100).optional().nullable().openapi({
  211. description: '原作者',
  212. example: '张医生'
  213. }),
  214. duration: z.number().int().positive().optional().nullable().openapi({
  215. description: '时长(秒/分钟)',
  216. example: 15
  217. }),
  218. createdAt: z.date().openapi({
  219. description: '创建时间',
  220. example: '2024-01-01T00:00:00Z'
  221. }),
  222. updatedAt: z.date().openapi({
  223. description: '更新时间',
  224. example: '2024-01-01T00:00:00Z'
  225. }),
  226. publishedAt: z.date().optional().nullable().openapi({
  227. description: '发布时间',
  228. example: '2024-01-01T00:00:00Z'
  229. })
  230. });
  231. export const CreateSilverKnowledgeDto = z.object({
  232. userId: z.coerce.number().int().positive().openapi({
  233. description: '发布用户ID',
  234. example: 1
  235. }),
  236. categoryId: z.number().int().positive().optional().openapi({
  237. description: '分类ID',
  238. example: 5
  239. }),
  240. title: z.string().max(255).openapi({
  241. description: '知识标题',
  242. example: '老年人健康饮食指南'
  243. }),
  244. content: z.string().openapi({
  245. description: '知识内容',
  246. example: '老年人健康饮食需要注意以下几点...'
  247. }),
  248. summary: z.string().optional().openapi({
  249. description: '知识摘要',
  250. example: '本指南详细介绍了老年人健康饮食的科学方法...'
  251. }),
  252. type: z.coerce.number().int().min(1).max(7).openapi({
  253. description: '知识类型',
  254. example: 1
  255. }),
  256. tags: z.string().optional().openapi({
  257. description: '标签(JSON数组)',
  258. example: '["健康养生", "老年人", "饮食"]'
  259. }),
  260. coverImage: z.string().max(500).url().optional().openapi({
  261. description: '封面图片URL',
  262. example: 'https://example.com/cover.jpg'
  263. }),
  264. attachments: z.string().optional().openapi({
  265. description: '附件URL(JSON数组)',
  266. example: '["https://example.com/attach1.pdf"]'
  267. }),
  268. keywords: z.string().optional().openapi({
  269. description: '搜索关键词',
  270. example: '健康,养生,老年人'
  271. }),
  272. source: z.string().max(255).optional().openapi({
  273. description: '知识来源',
  274. example: '中国老年学会'
  275. }),
  276. author: z.string().max(100).optional().openapi({
  277. description: '原作者',
  278. example: '张医生'
  279. }),
  280. duration: z.number().int().positive().optional().openapi({
  281. description: '时长(秒/分钟)',
  282. example: 15
  283. })
  284. });
  285. export const UpdateSilverKnowledgeDto = z.object({
  286. categoryId: z.number().int().positive().optional().openapi({
  287. description: '分类ID',
  288. example: 5
  289. }),
  290. title: z.string().max(255).optional().openapi({
  291. description: '知识标题',
  292. example: '老年人健康饮食指南'
  293. }),
  294. content: z.string().optional().openapi({
  295. description: '知识内容',
  296. example: '老年人健康饮食需要注意以下几点...'
  297. }),
  298. summary: z.string().optional().openapi({
  299. description: '知识摘要',
  300. example: '本指南详细介绍了老年人健康饮食的科学方法...'
  301. }),
  302. type: z.coerce.number().int().min(1).max(7).optional().openapi({
  303. description: '知识类型',
  304. example: 1
  305. }),
  306. tags: z.string().optional().openapi({
  307. description: '标签(JSON数组)',
  308. example: '["健康养生", "老年人", "饮食"]'
  309. }),
  310. coverImage: z.string().max(500).url().optional().openapi({
  311. description: '封面图片URL',
  312. example: 'https://example.com/cover.jpg'
  313. }),
  314. attachments: z.string().optional().openapi({
  315. description: '附件URL(JSON数组)',
  316. example: '["https://example.com/attach1.pdf"]'
  317. }),
  318. status: z.coerce.number().int().min(0).max(4).optional().openapi({
  319. description: '状态',
  320. example: 1
  321. }),
  322. keywords: z.string().optional().openapi({
  323. description: '搜索关键词',
  324. example: '健康,养生,老年人'
  325. }),
  326. source: z.string().max(255).optional().openapi({
  327. description: '知识来源',
  328. example: '中国老年学会'
  329. }),
  330. author: z.string().max(100).optional().openapi({
  331. description: '原作者',
  332. example: '张医生'
  333. }),
  334. duration: z.number().int().positive().optional().openapi({
  335. description: '时长(秒/分钟)',
  336. example: 15
  337. }),
  338. isFeatured: z.coerce.number().int().min(0).max(1).optional().openapi({
  339. description: '是否推荐',
  340. example: 1
  341. }),
  342. featuredAt: z.date().optional().nullable().openapi({
  343. description: '推荐时间',
  344. example: '2024-01-01T00:00:00Z'
  345. }),
  346. sortOrder: z.number().int().optional().openapi({
  347. description: '排序顺序',
  348. example: 1
  349. })
  350. });