NestJS实战05-完成当日目标和长期目标模块

by 雪隐 from juejin.cn/user/143341...

本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权

概要

上一章介绍了如何在NestJS中使用Moogoose,这一章继续完成当日任务和长期目标的2个模块。

当日任务和长期目标的功能介绍

  • 首页 首页我的想法是一个大的日历展示,然后每个日期里面每日任务的标题内容。绿色的点是已完成,黄色的点代买没有完成。

  • 当日任务页面 当日任务有增删改查,还有复制前一日的任务,批量删除等。

  • 长期目标页面 长期目标的内容主要是一些增删改查和批量删除等。

当日任务模块

  • 根据上面的内容展示可以知道,当日任务的主要功能有:
  1. 传统的增删改查。
  2. 批量删除
  3. 根据年月取得某个月的任务
  4. 根据某个日期取得某天的任务
  5. 根据日期复制任务到当天
  • 表的模型如下:

这里为 title添加设置了索引@Prop({ index: true }),是为了以后可能会扩展应用,发生检索标题的情况。另外,给isCompleted设置了默认初期值,默认状态是任务未完成的状态。

ts 复制代码
// task.entity.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type TaskDocument = Task & Document;

@Schema({ timestamps: true }) // 使用timestamps自动生成createdAt和updatedAt字段
export class Task {

    // 任务名字
    @Prop({ index: true }) // 为title字段添加索引,提高查询速度
    title: string;

    // 任务内容
    @Prop()
    content: string;

    // 是否完成
    @Prop({ default: false })
    isCompleted: boolean;
}

export const TaskSchema = SchemaFactory.createForClass(Task);
  • controller层如下:

基本上就是调用下,服务层代码就不多做介绍了。

ts 复制代码
// tasks.controller.ts
// 省略导入内容。。。。

@Controller('tasks')
export class TasksController {
  constructor(private readonly tasksService: TasksService) {}

  @Post()
  create(@Body() createTaskDto: CreateTaskDto) {
    return this.tasksService.create(createTaskDto);
  }

  @Get()
  findAll() {
    return this.tasksService.findAll();
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.tasksService.findOne(id);
  }

  @Patch(':id')
  update(@Param('id') id: string, @Body() updateTaskDto: UpdateTaskDto) {
    return this.tasksService.update(id, updateTaskDto);
  }

  @Delete(':id')
  remove(@Param('id') id: string): Promise<mongo.DeleteResult> {
    return this.tasksService.remove(id);
  }

  @Post('search')
  search(@Body() searchTaskDto: SearchTaskDto) {
    return this.tasksService.search(searchTaskDto);
  }

  @Delete('delete/batch')
  @HttpCode(204) // 无内容的响应状态码
  async removeMany(@Body('ids') ids: string[]): Promise<mongo.DeleteResult> {
    return this.tasksService.removeMany(ids);
  }

  @Get('by-month/:year/:month')
  async getTasksByMonth(@Param() monthYearTaskDto: MonthYearTaskDto) {
    const tasks = await this.tasksService.getTasksByYearAndMonth(
      monthYearTaskDto,
    );
    return tasks;
  }

  @Post('by-date')
  async getTasksByDate(@Body() dateTaskDto: DateTaskDto) {
    const tasks = await this.tasksService.getTasksByDate(dateTaskDto);
    return tasks;
  }

  @Post('copy-date')
  async copyTasksToDate(@Body() dateTaskDto: DateTaskDto) {
    const tasks = await this.tasksService.copyTasksToDate(dateTaskDto);
    return tasks;
  }
}
  • service层如下:
  1. 导入 Mongoose :在代码的开头,可以看到通过 import { InjectModel } from '@nestjs/mongoose'; 导入了 Mongoose 模块。这是用于与 MongoDB 数据库进行交互的关键。
  2. 模型创建 :在构造函数中,你使用了 @InjectModel(Task.name) 装饰器来注入了一个 Mongoose 模型(taskModel)。这个模型是基于 Task 实体的,它充当了任务数据在应用中的接口。
  3. 创建任务 :在 create 方法中,使用了 this.taskModel.create(createTaskDto) 来创建一个新的任务文档,并返回了一个 Promise。这是 Mongoose 提供的用于在数据库中插入新文档的方法。如果插入失败,抛出了自定义异常。
  4. 查询任务 :在 findAllfindOne 方法中,使用 this.taskModel.find()this.taskModel.findById() 来查询数据库中的任务。这些方法是 Mongoose 提供的查询工具,允许你按条件检索文档。
  5. 更新任务 :在 update 方法中,使用了 this.taskModel.findByIdAndUpdate() 方法来更新任务。这是 Mongoose 提供的方法,用于按照给定的条件更新文档。
  6. 删除任务 :在 remove 方法中,使用了 this.taskModel.deleteOne() 方法来删除任务文档。这也是 Mongoose 提供的一个用于删除文档的方法。
  7. 聚合操作 :在 getTasksByYearAndMonth 方法中,使用了 this.taskModel.aggregate() 方法来执行聚合操作,以按日期分组任务。这是 Mongoose 中用于执行聚合查询的功能,可以用于更复杂的数据处理需求。
  8. 自定义异常 :在各个方法中都使用了自定义异常类 BusinessException 来处理错误情况。确保应用程序能够更好地处理和报告错误。
ts 复制代码
// tasks.service.ts
// 省略导入内容。。。。

@Injectable()
export class TasksService {
  constructor(@InjectModel(Task.name) private taskModel: Model<Task>) {}

  async create(createTaskDto: CreateTaskDto): Promise<Task> {
    const task = await this.taskModel.create(createTaskDto);
    if (!task) {
      throw new BusinessException('当日任务创建失败');
    }
    return task;
  }

  async findAll(): Promise<Task[]> {
    const tasks = await this.taskModel.find().exec();
    return tasks;
  }

  async findOne(id: string): Promise<Task> {
    const task = await this.taskModel.findById(id).exec();
    if (!task) {
      throw new BusinessException('未找到指定的任务');
    }
    return task;
  }

  async update(id: string, updateTaskDto: UpdateTaskDto): Promise<Task> {
    const updatedTask = await this.taskModel
      .findByIdAndUpdate(id, updateTaskDto, { new: true })
      .exec();
    if (!updatedTask) {
      throw new BusinessException('任务更新失败');
    }
    return updatedTask;
  }

  async remove(id: string): Promise<mongo.DeleteResult> {
    const remove = await this.taskModel.deleteOne({ _id: id }).exec();
    if (remove.deletedCount === 0) {
      throw new BusinessException('未找到要删除的任务');
    }
    return remove;
  }

  async search(
    searchTaskDto: SearchTaskDto,
  ): Promise<{ data: Task[]; total: number }> {
    const { startDate, endDate, page = 1, limit = 20 } = searchTaskDto;

    const queryCondition = {};

    // 如果提供了startDate和endDate,才加入查询条件
    if (startDate && endDate) {
      queryCondition['createdAt'] = {
        $gte: new Date(startDate + 'T00:00:00Z'), // 开始于这一天的开始
        $lte: new Date(endDate + 'T23:59:59Z'), // 结束于这一天的结束
      };
    }

    // 查询特定时间范围内的任务数量,为前端提供总页数计算的数据
    const total = await this.taskModel.countDocuments(queryCondition);

    // 根据 createdAt 字段查询任务并实现分页
    const tasks = await this.taskModel
      .find(queryCondition)
      .skip((page - 1) * limit) // 跳过之前页的任务
      .limit(limit) // 限制返回的任务数量
      .exec();

    return {
      data: tasks,
      total,
    };
  }

  async removeMany(ids: string[]): Promise<mongo.DeleteResult> {
    const res = await this.taskModel.deleteMany({ _id: { $in: ids } }).exec();
    if (res.deletedCount === 0) {
      throw new BusinessException('没有找到要删除的任务');
    }
    return res;
  }

  /**
   * 根据年月取得任务
   * @param year
   * @returns
   */
  async getTasksByYearAndMonth(monthYearTaskDto: MonthYearTaskDto) {
    const { year, month } = monthYearTaskDto;
    // 计算查询的起始日期和结束日期
    const startDate = new Date(year, month - 1, 1); // 月份从0开始,所以要减去1
    const endDate = new Date(year, month, 0); // 月份从0开始,最后一天是当月0号

    // 使用聚合操作按日期分组数据
    const result = await this.taskModel.aggregate([
      {
        $match: {
          createdAt: {
            $gte: startDate,
            $lte: endDate,
          },
        },
      },
      {
        $group: {
          _id: { $dayOfMonth: '$createdAt' },
          tasks: {
            $push: {
              type: {
                $cond: {
                  if: { $eq: ['$isCompleted', true] }, // 如果 isCompleted 为 true,则为 success,否则为 warning
                  then: 'success',
                  else: 'warning',
                },
              },
              content: '$title', // 您可能需要根据实际存储的字段名进行更改
            },
          },
        },
      },
      {
        $sort: {
          _id: 1,
        },
      },
    ]);

    // 将结果转换为所需的格式
    const formattedResult = {};
    result.forEach((item) => {
      formattedResult[item._id] = item.tasks;
    });

    return formattedResult;
  }

  async getTasksByDate(dateTaskDto: DateTaskDto) {
    const { date } = dateTaskDto;
    // 使用聚合操作按日期分组数据
    const result = await this.taskModel.aggregate([
      {
        $match: {
          createdAt: {
            $gte: new Date(
              dayjs(date).year(),
              dayjs(date).month(),
              dayjs(date).date(),
              0,
              0,
              0,
            ), // 开始于指定日期的开始
            $lte: new Date(
              dayjs(date).year(),
              dayjs(date).month(),
              dayjs(date).date(),
              23,
              59,
              59,
            ), // 结束于指定日期的结束
          },
        },
      },
      {
        $project: {
          type: { $ifNull: ['$type', 'normal'] }, // 如果没有类型字段,默认为'normal'
          title: '$title', // 根据实际字段进行更改
          content: '$content', // 根据实际字段进行更改
        },
      },
    ]);

    return result;
  }

  async copyTasksToDate(dateTaskDto: DateTaskDto): Promise<void> {
    // 获取特定日期的任务列表
    const tasks = await this.getTasksByDate(dateTaskDto);

    // 遍历任务列表并复制到今天
    for (const task of tasks) {
      const createTaskDto: CreateTaskDto = {
        title: task.title,
        content: task.content,
        isCompleted: false, // 如果需要复制完成状态,请设置为 task.isCompleted
      };

      // 创建新任务并保存到数据库
      await this.create(createTaskDto);
    }
  }
}

长期任务模块

长期任务的主要功能就是,由于实现方法和当日任务差不多,除了表模型以外。大家直接看代码就行了,这里不多做介绍了。

  1. 传统增删改查。
  2. 批量删除。
  • 表的模型如下:
ts 复制代码
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type LongTermGoalDocument = LongTermGoal & Document;

@Schema({ timestamps: true }) // 使用timestamps自动生成createdAt和updatedAt字段
export class LongTermGoal {

    // 目标标题
    @Prop({ index: true }) // 为title字段添加索引,提高查询速度
    title: string;

    // 目标内容
    @Prop()
    content: string;

    // 目标截止日期
    @Prop()
    deadline: Date;

    // 是否完成
    @Prop({ default: false })
    isCompleted: boolean;

    // 是否已警告(例如,如果超过了截止日期)
    @Prop({ default: false })
    isWarned: boolean;
}

export const LongTermGoalSchema = SchemaFactory.createForClass(LongTermGoal);

前端页面做成

由于前端不是本章的重点,就不错做介绍了,随便做的,可以用就行了。前端页面的代码里面back-base请自提。

本地运行

  • 先运行Mongo的docker容器(前一章容器的yml已经提供给大家了)
shall 复制代码
docker-compose up -d
  • 打开back-base代码,在根目录运行命令:
shall 复制代码
npm run start:dev
  • 打开front-base代码,在根目录运行命令:
shall 复制代码
npm run dev

总结

这一章比较简单,把当日任务和长期目标2个模块做好了。如果这篇文章对大家有帮助请点赞和评论🙏!

相关推荐
崔庆才丨静觅3 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了4 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅4 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅5 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊5 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax