From 812ac5c21ef2f3df2f9a51d094e4fe33b9ba7b1b Mon Sep 17 00:00:00 2001 From: richarjiang Date: Thu, 14 Aug 2025 19:16:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai-coach,checkins):=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E8=BD=AF=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在AI教练和打卡模块中添加deleted字段,将物理删除改为软删除: - 在AiConversation和AiMessage模型中添加deleted布尔字段 - 在Checkin模型中添加deleted字段 - 更新所有查询条件添加deleted: false过滤 - 修改删除操作为标记deleted: true而非物理删除 - 在打卡服务中添加重复记录检查逻辑 --- src/ai-coach/ai-coach.service.ts | 14 ++++---- src/ai-coach/models/ai-conversation.model.ts | 7 ++++ src/ai-coach/models/ai-message.model.ts | 7 ++++ src/checkins/checkins.service.ts | 34 ++++++++++++++++++-- src/checkins/models/checkin.model.ts | 6 ++++ 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/ai-coach/ai-coach.service.ts b/src/ai-coach/ai-coach.service.ts index e5b0b31..1fa457a 100644 --- a/src/ai-coach/ai-coach.service.ts +++ b/src/ai-coach/ai-coach.service.ts @@ -54,7 +54,7 @@ export class AiCoachService { buildChatHistory = async (userId: string, conversationId: string) => { const history = await AiMessage.findAll({ - where: { userId, conversationId }, + where: { userId, conversationId, deleted: false }, order: [['created_at', 'ASC']], }); @@ -128,7 +128,7 @@ export class AiCoachService { const pageSize = Math.min(50, Math.max(1, params.pageSize || 20)); const offset = (page - 1) * pageSize; const { rows, count } = await AiConversation.findAndCountAll({ - where: { userId }, + where: { userId, deleted: false }, order: [['last_message_at', 'DESC']], offset, limit: pageSize, @@ -147,10 +147,10 @@ export class AiCoachService { } async getConversationDetail(userId: string, conversationId: string) { - const conv = await AiConversation.findOne({ where: { id: conversationId, userId } }); + const conv = await AiConversation.findOne({ where: { id: conversationId, userId, deleted: false } }); if (!conv) return null; const messages = await AiMessage.findAll({ - where: { userId, conversationId }, + where: { userId, conversationId, deleted: false }, order: [['created_at', 'ASC']], }); return { @@ -163,10 +163,10 @@ export class AiCoachService { } async deleteConversation(userId: string, conversationId: string): Promise { - const conv = await AiConversation.findOne({ where: { id: conversationId, userId } }); + const conv = await AiConversation.findOne({ where: { id: conversationId, userId, deleted: false } }); if (!conv) return false; - await AiMessage.destroy({ where: { userId, conversationId } }); - await AiConversation.destroy({ where: { id: conversationId, userId } }); + await AiMessage.update({ deleted: true }, { where: { userId, conversationId } }); + await AiConversation.update({ deleted: true }, { where: { id: conversationId, userId } }); return true; } diff --git a/src/ai-coach/models/ai-conversation.model.ts b/src/ai-coach/models/ai-conversation.model.ts index 75e4c14..dc66530 100644 --- a/src/ai-coach/models/ai-conversation.model.ts +++ b/src/ai-coach/models/ai-conversation.model.ts @@ -46,6 +46,13 @@ export class AiConversation extends Model { }) declare updatedAt: Date; + @Column({ + type: DataType.BOOLEAN, + allowNull: false, + defaultValue: false, + }) + declare deleted: boolean; + @HasMany(() => AiMessage) declare messages?: AiMessage[]; } diff --git a/src/ai-coach/models/ai-message.model.ts b/src/ai-coach/models/ai-message.model.ts index 51f3174..9b13adb 100644 --- a/src/ai-coach/models/ai-message.model.ts +++ b/src/ai-coach/models/ai-message.model.ts @@ -65,6 +65,13 @@ export class AiMessage extends Model { }) declare updatedAt: Date; + @Column({ + type: DataType.BOOLEAN, + allowNull: false, + defaultValue: false, + }) + declare deleted: boolean; + @BelongsTo(() => AiConversation) declare conversation?: AiConversation; } diff --git a/src/checkins/checkins.service.ts b/src/checkins/checkins.service.ts index 9477cf9..8a018c4 100644 --- a/src/checkins/checkins.service.ts +++ b/src/checkins/checkins.service.ts @@ -19,6 +19,21 @@ export class CheckinsService { ) { } async create(dto: CreateCheckinDto): Promise { + // 检查是否已存在未删除的记录 + const existingRecord = await this.checkinModel.findOne({ + where: { + userId: dto.userId, + workoutId: dto.workoutId || null, + planId: dto.planId || null, + checkinDate: dto.checkinDate || null, + deleted: false, + }, + }); + + if (existingRecord) { + return { code: ResponseCode.SUCCESS, message: 'success', data: existingRecord.toJSON() }; + } + const record = await this.checkinModel.create({ userId: dto.userId, workoutId: dto.workoutId || null, @@ -43,7 +58,12 @@ export class CheckinsService { } async update(dto: UpdateCheckinDto, userId: string): Promise { - const record = await this.checkinModel.findByPk(dto.id); + const record = await this.checkinModel.findOne({ + where: { + id: dto.id, + deleted: false, + }, + }); if (!record) { throw new NotFoundException('打卡记录不存在'); } @@ -75,7 +95,12 @@ export class CheckinsService { } async complete(dto: CompleteCheckinDto, userId: string): Promise { - const record = await this.checkinModel.findByPk(dto.id); + const record = await this.checkinModel.findOne({ + where: { + id: dto.id, + deleted: false, + }, + }); if (!record) { throw new NotFoundException('打卡记录不存在'); } @@ -114,7 +139,8 @@ export class CheckinsService { if (record.userId !== userId) { throw new ForbiddenException('无权操作该打卡记录'); } - await record.destroy(); + record.deleted = true; + await record.save(); await this.activityLogsService.record({ userId: record.userId, entityType: ActivityEntityType.CHECKIN, @@ -139,6 +165,7 @@ export class CheckinsService { const rows = await this.checkinModel.findAll({ where: { userId, + deleted: false, [Op.or]: [ { checkinDate: target.format('YYYY-MM-DD') as any }, { @@ -170,6 +197,7 @@ export class CheckinsService { const rows = await this.checkinModel.findAll({ where: { userId, + deleted: false, [Op.or]: [ { checkinDate: { diff --git a/src/checkins/models/checkin.model.ts b/src/checkins/models/checkin.model.ts index bd91310..f8c3c84 100644 --- a/src/checkins/models/checkin.model.ts +++ b/src/checkins/models/checkin.model.ts @@ -111,6 +111,12 @@ export class Checkin extends Model { defaultValue: DataType.NOW, }) declare updatedAt: Date; + + @Column({ + type: DataType.BOOLEAN, + defaultValue: false, + }) + declare deleted: boolean; }