feat: 删除心情打卡和目标子任务API测试文件
- 移除test-goal-tasks.http和test-mood-checkins.http文件,清理不再使用的测试文件。 - 更新GoalsService中的目标删除逻辑,增加事务处理以确保数据一致性。 - 优化GoalTaskService中的任务生成逻辑,增加日志记录以便于调试和监控。
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { IsString, IsNotEmpty, IsOptional, IsEnum, IsInt, IsDateString, IsBoolean, Min, Max, IsArray, ValidateNested } from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { GoalRepeatType, GoalStatus } from '../models/goal.model';
|
||||
import { GoalRepeatType } from '../models/goal.model';
|
||||
|
||||
export class CustomRepeatRuleDto {
|
||||
@IsOptional()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/sequelize';
|
||||
import { Op, WhereOptions, Order } from 'sequelize';
|
||||
import { InjectModel, InjectConnection } from '@nestjs/sequelize';
|
||||
import { Op, WhereOptions, Order, Transaction } from 'sequelize';
|
||||
import { Sequelize } from 'sequelize-typescript';
|
||||
import { Goal, GoalStatus, GoalRepeatType } from './models/goal.model';
|
||||
import { GoalCompletion } from './models/goal-completion.model';
|
||||
import { GoalTask } from './models/goal-task.model';
|
||||
@@ -22,6 +23,8 @@ export class GoalsService {
|
||||
private readonly goalCompletionModel: typeof GoalCompletion,
|
||||
@InjectModel(GoalTask)
|
||||
private readonly goalTaskModel: typeof GoalTask,
|
||||
@InjectConnection()
|
||||
private readonly sequelize: Sequelize,
|
||||
private readonly goalTaskService: GoalTaskService,
|
||||
) { }
|
||||
|
||||
@@ -180,27 +183,58 @@ export class GoalsService {
|
||||
* 删除目标
|
||||
*/
|
||||
async deleteGoal(userId: string, goalId: string): Promise<boolean> {
|
||||
const transaction = await this.sequelize.transaction();
|
||||
|
||||
try {
|
||||
// 验证目标存在
|
||||
const goal = await this.goalModel.findOne({
|
||||
where: { id: goalId, userId, deleted: false },
|
||||
transaction,
|
||||
});
|
||||
|
||||
if (!goal) {
|
||||
await transaction.rollback();
|
||||
throw new NotFoundException('目标不存在');
|
||||
}
|
||||
|
||||
// 软删除目标
|
||||
await goal.update({ deleted: true });
|
||||
// 使用事务删除目标及其相关数据
|
||||
await Promise.all([
|
||||
// 软删除目标本身
|
||||
this.goalModel.update(
|
||||
{ deleted: true },
|
||||
{
|
||||
where: { id: goalId, userId, deleted: false },
|
||||
transaction
|
||||
}
|
||||
),
|
||||
|
||||
// 软删除目标完成记录
|
||||
this.goalCompletionModel.update(
|
||||
{ deleted: true },
|
||||
{
|
||||
where: { goalId, userId, deleted: false },
|
||||
transaction
|
||||
}
|
||||
),
|
||||
|
||||
// 软删除与目标关联的任务
|
||||
this.goalTaskModel.update(
|
||||
{ deleted: true },
|
||||
{
|
||||
where: { goalId, userId, deleted: false },
|
||||
transaction
|
||||
}
|
||||
),
|
||||
]);
|
||||
|
||||
// 软删除相关的完成记录
|
||||
await this.goalCompletionModel.update(
|
||||
{ deleted: true },
|
||||
{ where: { goalId, userId } }
|
||||
);
|
||||
// 提交事务
|
||||
await transaction.commit();
|
||||
|
||||
this.logger.log(`用户 ${userId} 删除了目标: ${goal.title}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
// 回滚事务
|
||||
await transaction.rollback();
|
||||
this.logger.error(`删除目标失败: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -42,8 +42,11 @@ export class GoalTaskService {
|
||||
}
|
||||
|
||||
const goals = await this.goalModel.findAll({ where });
|
||||
|
||||
this.logger.log(`为用户 ${userId} 找到 ${goals.length} 个活跃目标`);
|
||||
|
||||
for (const goal of goals) {
|
||||
this.logger.log(`开始为目标 ${goal.title} (${goal.repeatType}) 生成任务`);
|
||||
await this.generateTasksForGoal(goal);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -83,6 +86,9 @@ export class GoalTaskService {
|
||||
case GoalRepeatType.CUSTOM:
|
||||
await this.generateCustomTasks(goal, startDate, endDate, existingTasks);
|
||||
break;
|
||||
default:
|
||||
this.logger.warn(`未知的重复类型: ${goal.repeatType}`);
|
||||
break;
|
||||
}
|
||||
|
||||
// 更新过期任务状态
|
||||
@@ -191,41 +197,66 @@ export class GoalTaskService {
|
||||
existingTasks: GoalTask[]
|
||||
): Promise<void> {
|
||||
const today = dayjs();
|
||||
const generateUntil = today.add(3, 'month'); // 提前生成3个月的任务
|
||||
const generateUntil = today.add(6, 'month'); // 提前生成6个月的任务
|
||||
const actualEndDate = endDate.isBefore(generateUntil) ? endDate : generateUntil;
|
||||
|
||||
// 检查是否有自定义重复规则指定每月第几天
|
||||
let targetDayOfMonth = 1; // 默认每月1号
|
||||
if (goal.customRepeatRule && goal.customRepeatRule.dayOfMonth) {
|
||||
targetDayOfMonth = goal.customRepeatRule.dayOfMonth;
|
||||
this.logger.log(`目标 ${goal.title} 设置为每月第 ${targetDayOfMonth} 天`);
|
||||
}
|
||||
|
||||
// 从开始日期开始,逐月生成任务
|
||||
let current = startDate.startOf('month');
|
||||
let generatedCount = 0;
|
||||
|
||||
this.logger.log(`开始生成每月任务,目标日期:每月第 ${targetDayOfMonth} 天`);
|
||||
|
||||
while (current.isSameOrBefore(actualEndDate)) {
|
||||
const monthStart = current.startOf('month');
|
||||
const monthEnd = current.endOf('month');
|
||||
// 计算该月的目标日期
|
||||
const targetDate = current.date(targetDayOfMonth);
|
||||
|
||||
// 如果目标日期超出了该月的天数,则使用该月的最后一天
|
||||
const daysInMonth = current.daysInMonth();
|
||||
const actualTargetDate = targetDayOfMonth > daysInMonth ? current.date(daysInMonth) : targetDate;
|
||||
|
||||
// 检查是否已经过了该月的目标日期
|
||||
if (actualTargetDate.isBefore(today)) {
|
||||
this.logger.log(`跳过 ${current.format('YYYY年MM月')},目标日期 ${actualTargetDate.format('MM-DD')} 已过期`);
|
||||
current = current.add(1, 'month');
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否已存在该月的任务
|
||||
const existingTask = existingTasks.find(task => {
|
||||
const taskMonthStart = dayjs(task.startDate).startOf('month');
|
||||
return taskMonthStart.isSame(monthStart);
|
||||
const taskDate = dayjs(task.startDate);
|
||||
return taskDate.isSame(actualTargetDate, 'day');
|
||||
});
|
||||
|
||||
if (!existingTask && monthStart.isSameOrAfter(startDate)) {
|
||||
const taskTitle = `${goal.title} - ${current.format('YYYY年MM月')}`;
|
||||
if (!existingTask && actualTargetDate.isSameOrAfter(startDate)) {
|
||||
const taskTitle = `${goal.title} - ${actualTargetDate.format('YYYY年MM月DD日')}`;
|
||||
|
||||
await this.goalTaskModel.create({
|
||||
goalId: goal.id,
|
||||
userId: goal.userId,
|
||||
title: taskTitle,
|
||||
description: `每月目标:完成${goal.frequency}次`,
|
||||
startDate: monthStart.toDate(),
|
||||
endDate: monthEnd.toDate(),
|
||||
startDate: actualTargetDate.toDate(),
|
||||
endDate: actualTargetDate.toDate(), // 任务在当天完成
|
||||
targetCount: goal.frequency,
|
||||
currentCount: 0,
|
||||
status: TaskStatus.PENDING,
|
||||
});
|
||||
|
||||
generatedCount++;
|
||||
this.logger.log(`为目标 ${goal.title} 生成每月任务: ${taskTitle}`);
|
||||
}
|
||||
|
||||
current = current.add(1, 'month');
|
||||
}
|
||||
|
||||
this.logger.log(`为目标 ${goal.title} 生成了 ${generatedCount} 个每月任务`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,18 +269,26 @@ export class GoalTaskService {
|
||||
existingTasks: GoalTask[]
|
||||
): Promise<void> {
|
||||
if (!goal.customRepeatRule) {
|
||||
this.logger.warn(`目标 ${goal.title} 缺少自定义重复规则`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { weekdays } = goal.customRepeatRule;
|
||||
|
||||
this.logger.log(`为目标 ${goal.title} 生成自定义任务,重复规则: ${JSON.stringify(goal.customRepeatRule)}`);
|
||||
|
||||
if (weekdays && weekdays.length > 0) {
|
||||
// 按指定星期几生成任务
|
||||
const today = dayjs();
|
||||
const generateUntil = today.add(7, 'day');
|
||||
const generateUntil = today.add(4, 'week'); // 提前生成4周的任务,确保有足够的任务
|
||||
const actualEndDate = endDate.isBefore(generateUntil) ? endDate : generateUntil;
|
||||
|
||||
let current = startDate;
|
||||
// 从今天开始生成,如果开始日期晚于今天则从开始日期开始
|
||||
let current = startDate.isBefore(today) ? today : startDate;
|
||||
|
||||
let generatedCount = 0;
|
||||
|
||||
this.logger.log(`开始生成自定义任务,日期范围: ${current.format('YYYY-MM-DD')} 到 ${actualEndDate.format('YYYY-MM-DD')}`);
|
||||
|
||||
while (current.isSameOrBefore(actualEndDate)) {
|
||||
const dayOfWeek = current.day(); // 0=周日, 6=周六
|
||||
@@ -277,12 +316,17 @@ export class GoalTaskService {
|
||||
status: TaskStatus.PENDING,
|
||||
});
|
||||
|
||||
generatedCount++;
|
||||
this.logger.log(`为目标 ${goal.title} 生成自定义任务: ${taskTitle}`);
|
||||
}
|
||||
}
|
||||
|
||||
current = current.add(1, 'day');
|
||||
}
|
||||
|
||||
this.logger.log(`为目标 ${goal.title} 生成了 ${generatedCount} 个自定义任务`);
|
||||
} else {
|
||||
this.logger.warn(`目标 ${goal.title} 的自定义重复规则中没有指定星期几`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
### 目标子任务API测试文件
|
||||
|
||||
@baseUrl = http://localhost:3000
|
||||
@token = your-auth-token-here
|
||||
|
||||
### 1. 创建每日喝水目标
|
||||
POST {{baseUrl}}/goals
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "每日喝水",
|
||||
"description": "每天喝8杯水保持健康",
|
||||
"repeatType": "daily",
|
||||
"frequency": 8,
|
||||
"category": "健康",
|
||||
"startDate": "2024-01-01",
|
||||
"hasReminder": true,
|
||||
"reminderTime": "09:00"
|
||||
}
|
||||
|
||||
### 2. 创建每周运动目标
|
||||
POST {{baseUrl}}/goals
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "每周运动",
|
||||
"description": "每周运动3次,每次至少30分钟",
|
||||
"repeatType": "weekly",
|
||||
"frequency": 3,
|
||||
"category": "运动",
|
||||
"startDate": "2024-01-01",
|
||||
"targetCount": 52
|
||||
}
|
||||
|
||||
### 3. 创建自定义周期目标(周末阅读)
|
||||
POST {{baseUrl}}/goals
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "周末阅读",
|
||||
"description": "每个周末阅读1小时",
|
||||
"repeatType": "custom",
|
||||
"frequency": 1,
|
||||
"customRepeatRule": {
|
||||
"weekdays": [0, 6]
|
||||
},
|
||||
"category": "学习",
|
||||
"startDate": "2024-01-01"
|
||||
}
|
||||
|
||||
### 4. 获取目标列表(触发任务生成)
|
||||
GET {{baseUrl}}/goals?page=1&pageSize=10
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 5. 获取所有任务列表
|
||||
GET {{baseUrl}}/goals/tasks?page=1&pageSize=20
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 6. 获取今天的任务
|
||||
GET {{baseUrl}}/goals/tasks?startDate=2024-01-15&endDate=2024-01-15
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 7. 获取进行中的任务
|
||||
GET {{baseUrl}}/goals/tasks?status=pending
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 8. 获取特定目标的任务列表
|
||||
# 需要替换为实际的goalId
|
||||
GET {{baseUrl}}/goals/{goalId}/tasks
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 9. 完成任务 - 单次完成
|
||||
# 需要替换为实际的taskId
|
||||
POST {{baseUrl}}/goals/tasks/{taskId}/complete
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"count": 1,
|
||||
"notes": "早上喝了第一杯水"
|
||||
}
|
||||
|
||||
### 10. 完成任务 - 多次完成
|
||||
# 需要替换为实际的taskId
|
||||
POST {{baseUrl}}/goals/tasks/{taskId}/complete
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"count": 3,
|
||||
"notes": "连续喝了3杯水",
|
||||
"completedAt": "2024-01-15T14:30:00Z"
|
||||
}
|
||||
|
||||
### 11. 获取单个任务详情
|
||||
# 需要替换为实际的taskId
|
||||
GET {{baseUrl}}/goals/tasks/{taskId}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 12. 更新任务
|
||||
# 需要替换为实际的taskId
|
||||
PUT {{baseUrl}}/goals/tasks/{taskId}
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"notes": "修改了任务备注",
|
||||
"targetCount": 10
|
||||
}
|
||||
|
||||
### 13. 跳过任务
|
||||
# 需要替换为实际的taskId
|
||||
POST {{baseUrl}}/goals/tasks/{taskId}/skip
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"reason": "今天身体不舒服,暂时跳过"
|
||||
}
|
||||
|
||||
### 14. 获取任务统计(所有目标)
|
||||
GET {{baseUrl}}/goals/tasks/stats/overview
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 15. 获取特定目标的任务统计
|
||||
# 需要替换为实际的goalId
|
||||
GET {{baseUrl}}/goals/tasks/stats/overview?goalId={goalId}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 16. 获取目标详情(包含任务)
|
||||
# 需要替换为实际的goalId
|
||||
GET {{baseUrl}}/goals/{goalId}
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 17. 批量操作目标
|
||||
POST {{baseUrl}}/goals/batch
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"goalIds": ["goal-id-1", "goal-id-2"],
|
||||
"action": "pause"
|
||||
}
|
||||
|
||||
### 18. 获取过期任务
|
||||
GET {{baseUrl}}/goals/tasks?status=overdue
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 19. 获取已完成任务
|
||||
GET {{baseUrl}}/goals/tasks?status=completed&page=1&pageSize=10
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 20. 获取本周任务
|
||||
GET {{baseUrl}}/goals/tasks?startDate=2024-01-08&endDate=2024-01-14
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
### 测试场景说明
|
||||
|
||||
# 场景1:每日喝水目标测试流程
|
||||
# 1. 创建每日喝水目标(接口1)
|
||||
# 2. 获取目标列表触发任务生成(接口4)
|
||||
# 3. 查看今天的任务(接口6)
|
||||
# 4. 分多次完成喝水任务(接口9、10)
|
||||
# 5. 查看任务统计(接口14)
|
||||
|
||||
# 场景2:每周运动目标测试流程
|
||||
# 1. 创建每周运动目标(接口2)
|
||||
# 2. 获取本周任务(接口20)
|
||||
# 3. 完成运动任务(接口9)
|
||||
# 4. 查看特定目标的任务列表(接口8)
|
||||
|
||||
# 场景3:任务管理测试流程
|
||||
# 1. 查看所有待完成任务(接口7)
|
||||
# 2. 跳过某个任务(接口13)
|
||||
# 3. 更新任务信息(接口12)
|
||||
# 4. 查看过期任务(接口18)
|
||||
|
||||
### 注意事项
|
||||
# 1. 替换所有的 {goalId} 和 {taskId} 为实际的ID值
|
||||
# 2. 替换 {{token}} 为有效的认证令牌
|
||||
# 3. 根据实际情况调整日期参数
|
||||
# 4. 某些API需要先创建目标和任务后才能测试
|
||||
@@ -1,94 +0,0 @@
|
||||
### 心情打卡功能测试
|
||||
|
||||
### 1. 创建心情打卡 - 开心
|
||||
POST http://localhost:3000/mood-checkins
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"moodType": "happy",
|
||||
"intensity": 8,
|
||||
"description": "今天完成了重要的项目,心情很好!",
|
||||
"checkinDate": "2025-08-21",
|
||||
"metadata": {
|
||||
"tags": ["工作", "成就感"],
|
||||
"trigger": "完成项目里程碑"
|
||||
}
|
||||
}
|
||||
|
||||
### 2. 创建心情打卡 - 焦虑
|
||||
POST http://localhost:3000/mood-checkins
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"moodType": "anxious",
|
||||
"intensity": 6,
|
||||
"description": "明天有重要的会议,感到有些紧张",
|
||||
"checkinDate": "2025-08-21",
|
||||
"metadata": {
|
||||
"tags": ["工作", "压力"],
|
||||
"trigger": "重要会议前的紧张"
|
||||
}
|
||||
}
|
||||
|
||||
### 3. 创建心情打卡 - 心动
|
||||
POST http://localhost:3000/mood-checkins
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"moodType": "excited",
|
||||
"intensity": 9,
|
||||
"description": "看到喜欢的人发来消息,心跳加速",
|
||||
"checkinDate": "2025-08-21",
|
||||
"metadata": {
|
||||
"tags": ["感情", "甜蜜"],
|
||||
"trigger": "收到心仪对象的消息"
|
||||
}
|
||||
}
|
||||
|
||||
### 3. 获取今日心情打卡
|
||||
GET http://localhost:3000/mood-checkins/daily
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
|
||||
### 4. 获取指定日期心情打卡
|
||||
GET http://localhost:3000/mood-checkins/daily?date=2025-08-21
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
|
||||
### 5. 获取心情打卡历史
|
||||
GET http://localhost:3000/mood-checkins/history?startDate=2025-08-01&endDate=2025-08-31
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
|
||||
### 6. 获取心情打卡历史(按类型过滤)
|
||||
GET http://localhost:3000/mood-checkins/history?startDate=2025-08-01&endDate=2025-08-31&moodType=happy
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
|
||||
### 7. 获取心情统计数据
|
||||
GET http://localhost:3000/mood-checkins/statistics?startDate=2025-08-01&endDate=2025-08-31
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
|
||||
### 8. 更新心情打卡
|
||||
PUT http://localhost:3000/mood-checkins
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": "MOOD_CHECKIN_ID",
|
||||
"moodType": "thrilled",
|
||||
"intensity": 9,
|
||||
"description": "更新后的心情描述 - 非常兴奋!",
|
||||
"metadata": {
|
||||
"tags": ["更新", "兴奋"],
|
||||
"trigger": "收到好消息"
|
||||
}
|
||||
}
|
||||
|
||||
### 9. 删除心情打卡
|
||||
DELETE http://localhost:3000/mood-checkins
|
||||
Authorization: Bearer YOUR_JWT_TOKEN
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": "MOOD_CHECKIN_ID"
|
||||
}
|
||||
Reference in New Issue
Block a user