|
@@ -0,0 +1,824 @@
|
|
|
|
|
+# 银龄库实体设计方案
|
|
|
|
|
+
|
|
|
|
|
+## 实体完整性分析结论
|
|
|
|
|
+
|
|
|
|
|
+经过详细分析,现有系统缺少完整的银龄库实体体系,需要新建以下核心实体:
|
|
|
|
|
+
|
|
|
|
|
+## 实体架构设计
|
|
|
|
|
+
|
|
|
|
|
+### 1. 银龄用户主档案实体 (SilverUserProfile)
|
|
|
|
|
+位置:`src/server/modules/silver-users/silver-user-profile.entity.ts`
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
|
|
|
|
+import { z } from '@hono/zod-openapi';
|
|
|
|
|
+
|
|
|
|
|
+export enum Gender {
|
|
|
|
|
+ MALE = 1,
|
|
|
|
|
+ FEMALE = 2,
|
|
|
|
|
+ OTHER = 3
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export enum CertificationStatus {
|
|
|
|
|
+ UNCERTIFIED = 0,
|
|
|
|
|
+ PENDING = 1,
|
|
|
|
|
+ CERTIFIED = 2,
|
|
|
|
|
+ REJECTED = 3
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export enum JobSeekingStatus {
|
|
|
|
|
+ NOT_SEEKING = 0,
|
|
|
|
|
+ ACTIVELY_SEEKING = 1,
|
|
|
|
|
+ OPEN_TO_OPPORTUNITIES = 2
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Entity('silver_user_profiles')
|
|
|
|
|
+export class SilverUserProfile {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
|
|
+ id!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ userId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ // 基本信息
|
|
|
|
|
+ @Column({ name: 'real_name', type: 'varchar', length: 50 })
|
|
|
|
|
+ realName!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'nickname', type: 'varchar', length: 50, nullable: true })
|
|
|
|
|
+ nickname!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'organization', type: 'varchar', length: 255, nullable: true })
|
|
|
|
|
+ organization!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'age', type: 'int', unsigned: true })
|
|
|
|
|
+ age!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'gender', type: 'tinyint', unsigned: true })
|
|
|
|
|
+ gender!: Gender;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'phone', type: 'varchar', length: 20 })
|
|
|
|
|
+ phone!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'email', type: 'varchar', length: 255, nullable: true })
|
|
|
|
|
+ email!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'avatar_url', type: 'varchar', length: 500, nullable: true })
|
|
|
|
|
+ avatarUrl!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'personal_intro', type: 'text', nullable: true })
|
|
|
|
|
+ personalIntro!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'personal_skills', type: 'text', nullable: true })
|
|
|
|
|
+ personalSkills!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'personal_experience', type: 'text', nullable: true })
|
|
|
|
|
+ personalExperience!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ // 认证信息
|
|
|
|
|
+ @Column({ name: 'certification_status', type: 'tinyint', default: CertificationStatus.UNCERTIFIED })
|
|
|
|
|
+ certificationStatus!: CertificationStatus;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'certification_info', type: 'text', nullable: true })
|
|
|
|
|
+ certificationInfo!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ // 求职需求
|
|
|
|
|
+ @Column({ name: 'job_seeking_status', type: 'tinyint', default: JobSeekingStatus.NOT_SEEKING })
|
|
|
|
|
+ jobSeekingStatus!: JobSeekingStatus;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'job_seeking_requirements', type: 'text', nullable: true })
|
|
|
|
|
+ jobSeekingRequirements!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ // 统计信息
|
|
|
|
|
+ @Column({ name: 'total_points', type: 'int', unsigned: true, default: 0 })
|
|
|
|
|
+ totalPoints!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'resume_count', type: 'int', unsigned: true, default: 0 })
|
|
|
|
|
+ resumeCount!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'application_count', type: 'int', unsigned: true, default: 0 })
|
|
|
|
|
+ applicationCount!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'time_bank_hours', type: 'decimal', precision: 10, scale: 2, default: 0 })
|
|
|
|
|
+ timeBankHours!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'knowledge_contributions', type: 'int', unsigned: true, default: 0 })
|
|
|
|
|
+ knowledgeContributions!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
|
|
+ createdAt!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ @UpdateDateColumn({ name: 'updated_at' })
|
|
|
|
|
+ updatedAt!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ @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 SilverUserProfileSchema = z.object({
|
|
|
|
|
+ id: z.number().int().positive().openapi({
|
|
|
|
|
+ description: '用户档案ID',
|
|
|
|
|
+ example: 1
|
|
|
|
|
+ }),
|
|
|
|
|
+ userId: z.number().int().positive().openapi({
|
|
|
|
|
+ description: '关联用户ID',
|
|
|
|
|
+ example: 1
|
|
|
|
|
+ }),
|
|
|
|
|
+ realName: z.string().max(50).openapi({
|
|
|
|
|
+ description: '真实姓名',
|
|
|
|
|
+ example: '张三'
|
|
|
|
|
+ }),
|
|
|
|
|
+ nickname: z.string().max(50).nullable().openapi({
|
|
|
|
|
+ description: '网名/昵称',
|
|
|
|
|
+ example: '银龄达人'
|
|
|
|
|
+ }),
|
|
|
|
|
+ organization: z.string().max(255).nullable().openapi({
|
|
|
|
|
+ description: '所属机构/单位',
|
|
|
|
|
+ example: '北京市老年协会'
|
|
|
|
|
+ }),
|
|
|
|
|
+ age: z.coerce.number().int().min(50).max(100).openapi({
|
|
|
|
|
+ description: '年龄',
|
|
|
|
|
+ example: 65
|
|
|
|
|
+ }),
|
|
|
|
|
+ gender: z.coerce.number().int().min(1).max(3).openapi({
|
|
|
|
|
+ description: '性别 1:男 2:女 3:其他',
|
|
|
|
|
+ example: 1
|
|
|
|
|
+ }),
|
|
|
|
|
+ phone: z.string().max(20).openapi({
|
|
|
|
|
+ description: '联系电话',
|
|
|
|
|
+ example: '13800138000'
|
|
|
|
|
+ }),
|
|
|
|
|
+ email: z.string().max(255).email().nullable().openapi({
|
|
|
|
|
+ description: '邮箱地址',
|
|
|
|
|
+ example: 'user@example.com'
|
|
|
|
|
+ }),
|
|
|
|
|
+ avatarUrl: z.string().max(500).url().nullable().openapi({
|
|
|
|
|
+ description: '头像URL',
|
|
|
|
|
+ example: 'https://example.com/avatar.jpg'
|
|
|
|
|
+ }),
|
|
|
|
|
+ personalIntro: z.string().nullable().openapi({
|
|
|
|
|
+ description: '个人简介',
|
|
|
|
|
+ example: '具有30年教育经验的退休教师,擅长老年教育和培训'
|
|
|
|
|
+ }),
|
|
|
|
|
+ personalSkills: z.string().nullable().openapi({
|
|
|
|
|
+ description: '个人技能',
|
|
|
|
|
+ example: '钢琴,书法,英语,老年心理辅导'
|
|
|
|
|
+ }),
|
|
|
|
|
+ personalExperience: z.string().nullable().openapi({
|
|
|
|
|
+ description: '个人履历',
|
|
|
|
|
+ example: '1985-2015 北京市第一中学教师,2015-至今 社区志愿者'
|
|
|
|
|
+ }),
|
|
|
|
|
+ certificationStatus: z.coerce.number().int().min(0).max(3).default(0).openapi({
|
|
|
|
|
+ description: '认证状态 0:未认证 1:待认证 2:已认证 3:认证失败',
|
|
|
|
|
+ example: 2
|
|
|
|
|
+ }),
|
|
|
|
|
+ certificationInfo: z.string().nullable().openapi({
|
|
|
|
|
+ description: '认证信息',
|
|
|
|
|
+ example: '教师资格证,心理咨询师证'
|
|
|
|
|
+ }),
|
|
|
|
|
+ jobSeekingStatus: z.coerce.number().int().min(0).max(2).default(0).openapi({
|
|
|
|
|
+ description: '求职状态 0:不求职 1:积极求职 2:观望中',
|
|
|
|
|
+ example: 1
|
|
|
|
|
+ }),
|
|
|
|
|
+ jobSeekingRequirements: z.string().nullable().openapi({
|
|
|
|
|
+ description: '求职需求',
|
|
|
|
|
+ example: '寻求教育咨询或培训类兼职工作,时间灵活'
|
|
|
|
|
+ }),
|
|
|
|
|
+ totalPoints: z.number().int().min(0).default(0).openapi({
|
|
|
|
|
+ description: '总积分',
|
|
|
|
|
+ example: 1000
|
|
|
|
|
+ }),
|
|
|
|
|
+ resumeCount: z.number().int().min(0).default(0).openapi({
|
|
|
|
|
+ description: '简历数量',
|
|
|
|
|
+ example: 3
|
|
|
|
|
+ }),
|
|
|
|
|
+ applicationCount: z.number().int().min(0).default(0).openapi({
|
|
|
|
|
+ description: '投递数量',
|
|
|
|
|
+ example: 5
|
|
|
|
|
+ }),
|
|
|
|
|
+ timeBankHours: z.coerce.number().min(0).default(0).openapi({
|
|
|
|
|
+ description: '时间银行小时数',
|
|
|
|
|
+ example: 120.5
|
|
|
|
|
+ }),
|
|
|
|
|
+ knowledgeContributions: z.number().int().min(0).default(0).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'
|
|
|
|
|
+ })
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+export const CreateSilverUserProfileDto = z.object({
|
|
|
|
|
+ userId: z.coerce.number().int().positive().openapi({
|
|
|
|
|
+ description: '关联用户ID',
|
|
|
|
|
+ example: 1
|
|
|
|
|
+ }),
|
|
|
|
|
+ realName: z.string().max(50).openapi({
|
|
|
|
|
+ description: '真实姓名',
|
|
|
|
|
+ example: '张三'
|
|
|
|
|
+ }),
|
|
|
|
|
+ nickname: z.string().max(50).optional().openapi({
|
|
|
|
|
+ description: '网名/昵称',
|
|
|
|
|
+ example: '银龄达人'
|
|
|
|
|
+ }),
|
|
|
|
|
+ organization: z.string().max(255).optional().openapi({
|
|
|
|
|
+ description: '所属机构/单位',
|
|
|
|
|
+ example: '北京市老年协会'
|
|
|
|
|
+ }),
|
|
|
|
|
+ age: z.coerce.number().int().min(50).max(100).openapi({
|
|
|
|
|
+ description: '年龄',
|
|
|
|
|
+ example: 65
|
|
|
|
|
+ }),
|
|
|
|
|
+ gender: z.coerce.number().int().min(1).max(3).openapi({
|
|
|
|
|
+ description: '性别 1:男 2:女 3:其他',
|
|
|
|
|
+ example: 1
|
|
|
|
|
+ }),
|
|
|
|
|
+ phone: z.string().max(20).openapi({
|
|
|
|
|
+ description: '联系电话',
|
|
|
|
|
+ example: '13800138000'
|
|
|
|
|
+ }),
|
|
|
|
|
+ email: z.string().max(255).email().optional().openapi({
|
|
|
|
|
+ description: '邮箱地址',
|
|
|
|
|
+ example: 'user@example.com'
|
|
|
|
|
+ }),
|
|
|
|
|
+ avatarUrl: z.string().max(500).url().optional().openapi({
|
|
|
|
|
+ description: '头像URL',
|
|
|
|
|
+ example: 'https://example.com/avatar.jpg'
|
|
|
|
|
+ }),
|
|
|
|
|
+ personalIntro: z.string().optional().openapi({
|
|
|
|
|
+ description: '个人简介',
|
|
|
|
|
+ example: '具有30年教育经验的退休教师'
|
|
|
|
|
+ }),
|
|
|
|
|
+ personalSkills: z.string().optional().openapi({
|
|
|
|
|
+ description: '个人技能',
|
|
|
|
|
+ example: '钢琴,书法,英语'
|
|
|
|
|
+ }),
|
|
|
|
|
+ personalExperience: z.string().optional().openapi({
|
|
|
|
|
+ description: '个人履历',
|
|
|
|
|
+ example: '1985-2015 北京市第一中学教师'
|
|
|
|
|
+ }),
|
|
|
|
|
+ jobSeekingRequirements: z.string().optional().openapi({
|
|
|
|
|
+ description: '求职需求',
|
|
|
|
|
+ example: '寻求教育咨询或培训类兼职工作'
|
|
|
|
|
+ })
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+export const UpdateSilverUserProfileDto = CreateSilverUserProfileDto.partial().extend({
|
|
|
|
|
+ certificationStatus: z.coerce.number().int().min(0).max(3).optional().openapi({
|
|
|
|
|
+ description: '认证状态',
|
|
|
|
|
+ example: 2
|
|
|
|
|
+ }),
|
|
|
|
|
+ certificationInfo: z.string().optional().openapi({
|
|
|
|
|
+ description: '认证信息',
|
|
|
|
|
+ example: '教师资格证,心理咨询师证'
|
|
|
|
|
+ }),
|
|
|
|
|
+ jobSeekingStatus: z.coerce.number().int().min(0).max(2).optional().openapi({
|
|
|
|
|
+ description: '求职状态',
|
|
|
|
|
+ example: 1
|
|
|
|
|
+ })
|
|
|
|
|
+});
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 2. 积分系统实体
|
|
|
|
|
+
|
|
|
|
|
+#### 2.1 积分主记录 (SilverPoint)
|
|
|
|
|
+位置:`src/server/modules/silver-users/silver-point.entity.ts`
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+@Entity('silver_points')
|
|
|
|
|
+export class SilverPoint {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
|
|
+ id!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ userId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'total_points', type: 'int', unsigned: true, default: 0 })
|
|
|
|
|
+ totalPoints!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'earned_points', type: 'int', unsigned: true, default: 0 })
|
|
|
|
|
+ earnedPoints!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'spent_points', type: 'int', unsigned: true, default: 0 })
|
|
|
|
|
+ spentPoints!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
|
|
+ createdAt!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ @UpdateDateColumn({ name: 'updated_at' })
|
|
|
|
|
+ updatedAt!: Date;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 2.2 积分流水记录 (SilverPointTransaction)
|
|
|
|
|
+位置:`src/server/modules/silver-users/silver-point-transaction.entity.ts`
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+export enum PointTransactionType {
|
|
|
|
|
+ EARN_SIGNIN = 1, // 签到获得
|
|
|
|
|
+ EARN_TASK = 2, // 任务完成获得
|
|
|
|
|
+ EARN_TIME_BANK = 3, // 时间银行获得
|
|
|
|
|
+ EARN_KNOWLEDGE = 4, // 知识贡献获得
|
|
|
|
|
+ SPEND_EXCHANGE = 10, // 积分兑换消耗
|
|
|
|
|
+ SPEND_TRANSFER = 11, // 积分转增消耗
|
|
|
|
|
+ ADJUSTMENT = 20 // 系统调整
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Entity('silver_point_transactions')
|
|
|
|
|
+export class SilverPointTransaction {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
|
|
+ id!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ userId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'points', type: 'int' }) // 正数表示增加,负数表示消耗
|
|
|
|
|
+ points!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'type', type: 'tinyint', unsigned: true })
|
|
|
|
|
+ type!: PointTransactionType;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'description', type: 'varchar', length: 255 })
|
|
|
|
|
+ description!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'related_id', type: 'int', unsigned: true, nullable: true })
|
|
|
|
|
+ relatedId!: number | null; // 关联业务ID
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'related_type', type: 'varchar', length: 50, nullable: true })
|
|
|
|
|
+ relatedType!: string | null; // 关联业务类型
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
|
|
+ createdAt!: Date;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 2.3 积分兑换记录 (SilverPointExchange)
|
|
|
|
|
+位置:`src/server/modules/silver-users/silver-point-exchange.entity.ts`
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+export enum ExchangeStatus {
|
|
|
|
|
+ PENDING = 0,
|
|
|
|
|
+ PROCESSING = 1,
|
|
|
|
|
+ COMPLETED = 2,
|
|
|
|
|
+ FAILED = 3,
|
|
|
|
|
+ CANCELLED = 4
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Entity('silver_point_exchanges')
|
|
|
|
|
+export class SilverPointExchange {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
|
|
+ id!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ userId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'item_name', type: 'varchar', length: 255 })
|
|
|
|
|
+ itemName!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'item_description', type: 'text', nullable: true })
|
|
|
|
|
+ itemDescription!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'points_required', type: 'int', unsigned: true })
|
|
|
|
|
+ pointsRequired!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'quantity', type: 'int', unsigned: true, default: 1 })
|
|
|
|
|
+ quantity!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'total_points', type: 'int', unsigned: true })
|
|
|
|
|
+ totalPoints!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'status', type: 'tinyint', default: ExchangeStatus.PENDING })
|
|
|
|
|
+ status!: ExchangeStatus;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'remark', type: 'text', nullable: true })
|
|
|
|
|
+ remark!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
|
|
+ createdAt!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ @UpdateDateColumn({ name: 'updated_at' })
|
|
|
|
|
+ updatedAt!: Date;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 2.4 积分转增记录 (SilverPointTransfer)
|
|
|
|
|
+位置:`src/server/modules/silver-users/silver-point-transfer.entity.ts`
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+export enum TransferStatus {
|
|
|
|
|
+ PENDING = 0,
|
|
|
|
|
+ COMPLETED = 1,
|
|
|
|
|
+ FAILED = 2,
|
|
|
|
|
+ CANCELLED = 3
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Entity('silver_point_transfers')
|
|
|
|
|
+export class SilverPointTransfer {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
|
|
+ id!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'from_user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ fromUserId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'to_user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ toUserId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'points', type: 'int', unsigned: true })
|
|
|
|
|
+ points!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'message', type: 'varchar', length: 500, nullable: true })
|
|
|
|
|
+ message!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'status', type: 'tinyint', default: TransferStatus.PENDING })
|
|
|
|
|
+ status!: TransferStatus;
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
|
|
+ createdAt!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ @UpdateDateColumn({ name: 'updated_at' })
|
|
|
|
|
+ updatedAt!: Date;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 3. 简历和投递系统增强
|
|
|
|
|
+
|
|
|
|
|
+#### 3.1 个人简历实体 (SilverResume)
|
|
|
|
|
+位置:`src/server/modules/silver-users/silver-resume.entity.ts`
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+@Entity('silver_resumes')
|
|
|
|
|
+export class SilverResume {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
|
|
+ id!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ userId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'title', type: 'varchar', length: 255 })
|
|
|
|
|
+ title!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'summary', type: 'text', nullable: true })
|
|
|
|
|
+ summary!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'skills', type: 'text', nullable: true })
|
|
|
|
|
+ skills!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'experience', type: 'text', nullable: true })
|
|
|
|
|
+ experience!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'education', type: 'text', nullable: true })
|
|
|
|
|
+ education!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'certifications', type: 'text', nullable: true })
|
|
|
|
|
+ certifications!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'is_default', type: 'tinyint', default: 0 })
|
|
|
|
|
+ isDefault!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'is_active', type: 'tinyint', default: 1 })
|
|
|
|
|
+ isActive!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
|
|
+ createdAt!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ @UpdateDateColumn({ name: 'updated_at' })
|
|
|
|
|
+ updatedAt!: Date;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 3.2 求职意向实体 (SilverJobSearch)
|
|
|
|
|
+位置:`src/server/modules/silver-users/silver-job-search.entity.ts`
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+export enum JobType {
|
|
|
|
|
+ FULL_TIME = 1,
|
|
|
|
|
+ PART_TIME = 2,
|
|
|
|
|
+ CONTRACT = 3,
|
|
|
|
|
+ VOLUNTEER = 4
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Entity('silver_job_searches')
|
|
|
|
|
+export class SilverJobSearch {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
|
|
+ id!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ userId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'position', type: 'varchar', length: 255 })
|
|
|
|
|
+ position!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'expected_salary', type: 'decimal', precision: 10, scale: 2, nullable: true })
|
|
|
|
|
+ expectedSalary!: number | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'work_location', type: 'varchar', length: 255, nullable: true })
|
|
|
|
|
+ workLocation!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'job_type', type: 'tinyint', unsigned: true })
|
|
|
|
|
+ jobType!: JobType;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'requirements', type: 'text', nullable: true })
|
|
|
|
|
+ requirements!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'is_active', type: 'tinyint', default: 1 })
|
|
|
|
|
+ isActive!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
|
|
+ createdAt!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ @UpdateDateColumn({ name: 'updated_at' })
|
|
|
|
|
+ updatedAt!: Date;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 3.3 增强现有的Application实体
|
|
|
|
|
+在现有基础上添加:
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 添加到Application实体
|
|
|
|
|
+@Column({ name: 'source', type: 'varchar', length: 50, default: 'platform' })
|
|
|
|
|
+source!: string; // 投递来源:platform, wechat, etc.
|
|
|
|
|
+
|
|
|
|
|
+@Column({ name: 'is_favorite', type: 'tinyint', default: 0 })
|
|
|
|
|
+isFavorite!: number;
|
|
|
|
|
+
|
|
|
|
|
+@Column({ name: 'is_followed', type: 'tinyint', default: 0 })
|
|
|
|
|
+isFollowed!: number;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 4. 时间银行系统
|
|
|
|
|
+
|
|
|
|
|
+#### 4.1 时间银行记录实体 (SilverTimeBank)
|
|
|
|
|
+位置:`src/server/modules/silver-users/silver-time-bank.entity.ts`
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+export enum WorkType {
|
|
|
|
|
+ VOLUNTEER = 1, // 志愿服务
|
|
|
|
|
+ PART_TIME = 2, // 兼职工作
|
|
|
|
|
+ CONSULTING = 3, // 咨询服务
|
|
|
|
|
+ TRAINING = 4, // 培训服务
|
|
|
|
|
+ OTHER = 5 // 其他服务
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Entity('silver_time_banks')
|
|
|
|
|
+export class SilverTimeBank {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
|
|
+ id!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ userId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'work_type', type: 'tinyint', unsigned: true })
|
|
|
|
|
+ workType!: WorkType;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'organization', type: 'varchar', length: 255 })
|
|
|
|
|
+ organization!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'position', type: 'varchar', length: 255, nullable: true })
|
|
|
|
|
+ position!: string | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'work_hours', type: 'decimal', precision: 10, scale: 2 })
|
|
|
|
|
+ workHours!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'earned_points', type: 'int', unsigned: true, default: 0 })
|
|
|
|
|
+ earnedPoints!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'description', type: 'text' })
|
|
|
|
|
+ description!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'work_photos', type: 'text', nullable: true })
|
|
|
|
|
+ workPhotos!: string | null; // JSON array of photo URLs
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'start_date', type: 'date' })
|
|
|
|
|
+ startDate!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'end_date', type: 'date', nullable: true })
|
|
|
|
|
+ endDate!: Date | null;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'is_verified', type: 'tinyint', default: 0 })
|
|
|
|
|
+ isVerified!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
|
|
+ createdAt!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ @UpdateDateColumn({ name: 'updated_at' })
|
|
|
|
|
+ updatedAt!: Date;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 5. 知识库系统
|
|
|
|
|
+
|
|
|
|
|
+#### 5.1 知识库内容实体 (SilverKnowledge)
|
|
|
|
|
+位置:`src/server/modules/silver-users/silver-knowledge.entity.ts`
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+export enum KnowledgeType {
|
|
|
|
|
+ ARTICLE = 1, // 文章
|
|
|
|
|
+ VIDEO = 2, // 视频
|
|
|
|
|
+ DOCUMENT = 3, // 文档
|
|
|
|
|
+ COURSE = 4, // 课程
|
|
|
|
|
+ EXPERIENCE = 5 // 经验分享
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export enum KnowledgeStatus {
|
|
|
|
|
+ DRAFT = 0, // 草稿
|
|
|
|
|
+ PUBLISHED = 1, // 已发布
|
|
|
|
|
+ HIDDEN = 2, // 已隐藏
|
|
|
|
|
+ DELETED = 3 // 已删除
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Entity('silver_knowledges')
|
|
|
|
|
+export class SilverKnowledge {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
|
|
+ id!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ userId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'title', type: 'varchar', length: 255 })
|
|
|
|
|
+ title!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'content', type: 'text' })
|
|
|
|
|
+ content!: string;
|
|
|
|
|
+
|
|
|
|
|
+ @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;
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
|
|
+ createdAt!: Date;
|
|
|
|
|
+
|
|
|
|
|
+ @UpdateDateColumn({ name: 'updated_at' })
|
|
|
|
|
+ updatedAt!: Date;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 5.2 知识库用户交互记录 (SilverKnowledgeInteraction)
|
|
|
|
|
+位置:`src/server/modules/silver-users/silver-knowledge-interaction.entity.ts`
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+export enum InteractionType {
|
|
|
|
|
+ VIEW = 1, // 阅读
|
|
|
|
|
+ DOWNLOAD = 2, // 下载
|
|
|
|
|
+ LIKE = 3, // 点赞
|
|
|
|
|
+ COMMENT = 4, // 评论
|
|
|
|
|
+ FAVORITE = 5, // 收藏
|
|
|
|
|
+ SHARE = 6 // 分享
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Entity('silver_knowledge_interactions')
|
|
|
|
|
+export class SilverKnowledgeInteraction {
|
|
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
|
|
+ id!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'user_id', type: 'int', unsigned: true })
|
|
|
|
|
+ userId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'knowledge_id', type: 'int', unsigned: true })
|
|
|
|
|
+ knowledgeId!: number;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'type', type: 'tinyint', unsigned: true })
|
|
|
|
|
+ type!: InteractionType;
|
|
|
|
|
+
|
|
|
|
|
+ @Column({ name: 'content', type: 'text', nullable: true })
|
|
|
|
|
+ content!: string | null; // 评论内容等
|
|
|
|
|
+
|
|
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
|
|
+ createdAt!: Date;
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 实体注册配置
|
|
|
|
|
+
|
|
|
|
|
+### 数据源注册 (src/server/data-source.ts)
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 新增实体导入
|
|
|
|
|
+import { SilverUserProfile } from "./modules/silver-users/silver-user-profile.entity"
|
|
|
|
|
+import { SilverPoint } from "./modules/silver-users/silver-point.entity"
|
|
|
|
|
+import { SilverPointTransaction } from "./modules/silver-users/silver-point-transaction.entity"
|
|
|
|
|
+import { SilverPointExchange } from "./modules/silver-users/silver-point-exchange.entity"
|
|
|
|
|
+import { SilverPointTransfer } from "./modules/silver-users/silver-point-transfer.entity"
|
|
|
|
|
+import { SilverResume } from "./modules/silver-users/silver-resume.entity"
|
|
|
|
|
+import { SilverJobSearch } from "./modules/silver-users/silver-job-search.entity"
|
|
|
|
|
+import { SilverTimeBank } from "./modules/silver-users/silver-time-bank.entity"
|
|
|
|
|
+import { SilverKnowledge } from "./modules/silver-users/silver-knowledge.entity"
|
|
|
|
|
+import { SilverKnowledgeInteraction } from "./modules/silver-users/silver-knowledge-interaction.entity"
|
|
|
|
|
+
|
|
|
|
|
+// 在entities数组中添加
|
|
|
|
|
+entities: [
|
|
|
|
|
+ // ...现有实体
|
|
|
|
|
+ SilverUserProfile,
|
|
|
|
|
+ SilverPoint,
|
|
|
|
|
+ SilverPointTransaction,
|
|
|
|
|
+ SilverPointExchange,
|
|
|
|
|
+ SilverPointTransfer,
|
|
|
|
|
+ SilverResume,
|
|
|
|
|
+ SilverJobSearch,
|
|
|
|
|
+ SilverTimeBank,
|
|
|
|
|
+ SilverKnowledge,
|
|
|
|
|
+ SilverKnowledgeInteraction
|
|
|
|
|
+]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 通用CRUD服务创建
|
|
|
|
|
+
|
|
|
|
|
+### 服务类模板
|
|
|
|
|
+每个实体对应的服务类继承GenericCrudService:
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+// src/server/modules/silver-users/silver-user-profile.service.ts
|
|
|
|
|
+import { GenericCrudService } from '@/server/utils/generic-crud.service';
|
|
|
|
|
+import { DataSource } from 'typeorm';
|
|
|
|
|
+import { SilverUserProfile } from './silver-user-profile.entity';
|
|
|
|
|
+
|
|
|
|
|
+export class SilverUserProfileService extends GenericCrudService<SilverUserProfile> {
|
|
|
|
|
+ constructor(dataSource: DataSource) {
|
|
|
|
|
+ super(dataSource, SilverUserProfile);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 可以添加自定义业务方法
|
|
|
|
|
+ async findByUserId(userId: number): Promise<SilverUserProfile | null> {
|
|
|
|
|
+ return this.repository.findOne({ where: { userId } });
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 路由配置方案
|
|
|
|
|
+
|
|
|
|
|
+### 路由结构
|
|
|
|
|
+```
|
|
|
|
|
+src/server/api/silver-users/
|
|
|
|
|
+├── index.ts
|
|
|
|
|
+├── profiles/
|
|
|
|
|
+│ ├── index.ts
|
|
|
|
|
+│ └── [id]/
|
|
|
|
|
+├── points/
|
|
|
|
|
+│ ├── index.ts
|
|
|
|
|
+│ └── [id]/
|
|
|
|
|
+├── resumes/
|
|
|
|
|
+├── job-searches/
|
|
|
|
|
+├── time-banks/
|
|
|
|
|
+└── knowledges/
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 前端API集成方案
|
|
|
|
|
+
|
|
|
|
|
+### 客户端API定义 (src/client/api.ts)
|
|
|
|
|
+```typescript
|
|
|
|
|
+// 银龄用户相关API
|
|
|
|
|
+export const silverUserClient = hc<SilverUserRoutes>('/api/v1', {
|
|
|
|
|
+ fetch: axiosFetch,
|
|
|
|
|
+}).api.v1['silver-users'];
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## 实施步骤
|
|
|
|
|
+
|
|
|
|
|
+1. **按顺序创建实体文件** - 先创建主实体,再创建关联实体
|
|
|
|
|
+2. **注册到数据源** - 在data-source.ts中添加所有新实体
|
|
|
|
|
+3. **创建服务类** - 每个实体对应的服务类
|
|
|
|
|
+4. **创建通用CRUD路由** - 使用createCrudRoutes快速生成
|
|
|
|
|
+5. **前端集成** - 创建客户端API和页面组件
|
|
|
|
|
+6. **数据迁移** - 如有需要,创建数据库迁移脚本
|
|
|
|
|
+
|
|
|
|
|
+## 注意事项
|
|
|
|
|
+
|
|
|
|
|
+1. **命名规范**:所有表名使用下划线命名法(silver_user_profiles)
|
|
|
|
|
+2. **字段规范**:所有字段必须包含name属性和中文注释
|
|
|
|
|
+3. **类型安全**:使用Zod Schema确保类型安全
|
|
|
|
|
+4. **用户跟踪**:使用userTracking配置自动记录操作人
|
|
|
|
|
+5. **性能优化**:为常用查询字段添加索引
|
|
|
|
|
+6. **数据一致性**:使用事务确保相关操作的原子性
|