by 雪隐 from juejin.cn/user/143341...
本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可联系授权
概要
上一章介绍了如何在NestJS
中使用Moogoose
,这一章继续完成当日任务和长期目标的2个模块。
当日任务和长期目标的功能介绍
-
首页 首页我的想法是一个大的日历展示,然后每个日期里面每日任务的标题内容。绿色的点是已完成,黄色的点代买没有完成。
-
当日任务页面 当日任务有增删改查,还有复制前一日的任务,批量删除等。
-
长期目标页面 长期目标的内容主要是一些增删改查和批量删除等。
当日任务模块
- 根据上面的内容展示可以知道,当日任务的主要功能有:
- 传统的增删改查。
- 批量删除
- 根据年月取得某个月的任务
- 根据某个日期取得某天的任务
- 根据日期复制任务到当天
- 表的模型如下:
这里为 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层如下:
- 导入 Mongoose :在代码的开头,可以看到通过
import { InjectModel } from '@nestjs/mongoose';
导入了 Mongoose 模块。这是用于与 MongoDB 数据库进行交互的关键。 - 模型创建 :在构造函数中,你使用了
@InjectModel(Task.name)
装饰器来注入了一个 Mongoose 模型(taskModel
)。这个模型是基于Task
实体的,它充当了任务数据在应用中的接口。 - 创建任务 :在
create
方法中,使用了this.taskModel.create(createTaskDto)
来创建一个新的任务文档,并返回了一个Promise
。这是 Mongoose 提供的用于在数据库中插入新文档的方法。如果插入失败,抛出了自定义异常。 - 查询任务 :在
findAll
、findOne
方法中,使用this.taskModel.find()
和this.taskModel.findById()
来查询数据库中的任务。这些方法是 Mongoose 提供的查询工具,允许你按条件检索文档。 - 更新任务 :在
update
方法中,使用了this.taskModel.findByIdAndUpdate()
方法来更新任务。这是 Mongoose 提供的方法,用于按照给定的条件更新文档。 - 删除任务 :在
remove
方法中,使用了this.taskModel.deleteOne()
方法来删除任务文档。这也是 Mongoose 提供的一个用于删除文档的方法。 - 聚合操作 :在
getTasksByYearAndMonth
方法中,使用了this.taskModel.aggregate()
方法来执行聚合操作,以按日期分组任务。这是 Mongoose 中用于执行聚合查询的功能,可以用于更复杂的数据处理需求。 - 自定义异常 :在各个方法中都使用了自定义异常类
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);
}
}
}
长期任务模块
长期任务的主要功能就是,由于实现方法和当日任务差不多,除了表模型以外。大家直接看代码就行了,这里不多做介绍了。
- 传统增删改查。
- 批量删除。
- 表的模型如下:
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个模块做好了。如果这篇文章对大家有帮助请点赞和评论🙏!