diff --git a/openapi.json b/openapi.json index 6fe40e4..2c30cf8 100644 --- a/openapi.json +++ b/openapi.json @@ -1,5 +1,5 @@ { - "hash": "0d281cd06010165c6b9979c2c30e2f98b9c6eb6cc5c49a1d2374ddd3397883e0", + "hash": "a499cd3dde02dd82d308aa0d3fcd3f082c1ee0cb50da627a71c9257fa5083fc4", "openapi": "3.0.0", "paths": { "/hello": { @@ -6270,6 +6270,17 @@ "createdAt" ] } + }, + "dateUnit": { + "type": "string", + "description": "Date unit for time-based grouping when createdAt is in group", + "enum": [ + "hour", + "day", + "week", + "month", + "year" + ] } } }, diff --git a/src/user/dto/aggregate.dto.ts b/src/user/dto/aggregate.dto.ts index c40a9ab..a512e2b 100644 --- a/src/user/dto/aggregate.dto.ts +++ b/src/user/dto/aggregate.dto.ts @@ -13,6 +13,14 @@ export enum GroupField { createdAt = 'createdAt', } +export enum DateUnit { + hour = 'hour', + day = 'day', + week = 'week', + month = 'month', + year = 'year', +} + export class AggregateUserDto { /** * The group by clause @@ -20,4 +28,11 @@ export class AggregateUserDto { @IsOptional() @IsEnum(GroupField, { each: true }) group?: GroupField[]; + + /** + * Date unit for time-based grouping when createdAt is in group + */ + @IsOptional() + @IsEnum(DateUnit) + dateUnit?: DateUnit; } diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 3d133c0..9dc0fbe 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -8,7 +8,7 @@ import { createHash, validateHash } from 'src/lib/crypt'; import { countTailZero, inferNumber } from 'src/lib/lang/number'; import { buildMongooseQuery, genAggGroupId, genSort, unWindGroupId } from 'src/mongo'; -import { AggregateUserDto } from './dto/aggregate.dto'; +import { AggregateUserDto, DateUnit, GroupField } from './dto/aggregate.dto'; import { CreateUserDto } from './dto/create-user.dto'; import { ListUsersQuery } from './dto/list-users.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @@ -180,8 +180,23 @@ export class UserService { */ aggregate(query: ListUsersQuery, dto: AggregateUserDto): Promise { const { filter, offset, limit, sort } = buildMongooseQuery(wrapFilter(query)); - const { group = [] } = dto; - const groupId = genAggGroupId(group); + const { group = [], dateUnit = DateUnit.day } = dto; + + // 处理时间字段分组 + let groupId: any = {}; + const hasCreatedAt = group.includes(GroupField.createdAt); + + if (hasCreatedAt) { + // 分离出非时间字段 + const nonTimeFields = group.filter((field) => field !== GroupField.createdAt); + // 为时间字段生成特殊的分组ID + groupId = { + ...genAggGroupId(nonTimeFields), + createdAt: this.getDateGroupExpression(dateUnit), + }; + } else { + groupId = genAggGroupId(group); + } // 特殊字段需要先进行 $unwind const unwindFields = ['labels', 'groups', 'roles']; @@ -218,4 +233,44 @@ export class UserService { return this.userModel.aggregate(pipeline); } + + private getDateGroupExpression(dateUnit: DateUnit) { + const dateExpression = '$createdAt'; + + switch (dateUnit) { + case DateUnit.hour: + return { + year: { $year: dateExpression }, + month: { $month: dateExpression }, + day: { $dayOfMonth: dateExpression }, + hour: { $hour: dateExpression }, + }; + case DateUnit.day: + return { + year: { $year: dateExpression }, + month: { $month: dateExpression }, + day: { $dayOfMonth: dateExpression }, + }; + case DateUnit.week: + return { + year: { $year: dateExpression }, + week: { $week: dateExpression }, + }; + case DateUnit.month: + return { + year: { $year: dateExpression }, + month: { $month: dateExpression }, + }; + case DateUnit.year: + return { + year: { $year: dateExpression }, + }; + default: + return { + year: { $year: dateExpression }, + month: { $month: dateExpression }, + day: { $dayOfMonth: dateExpression }, + }; + } + } }