feat: 生成活动接口

This commit is contained in:
richarjiang
2025-08-21 14:28:15 +08:00
parent 94e1b124df
commit 73f53ac5e4
8 changed files with 413 additions and 2 deletions

View File

@@ -0,0 +1,137 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { UserActivity, ActivityType, ActivityLevel } from '../models/user-activity.model';
import { CreateUserActivityDto, UserActivitySummaryDto } from '../dto/user-activity.dto';
import { Op } from 'sequelize';
import * as dayjs from 'dayjs';
@Injectable()
export class UserActivityService {
private readonly logger = new Logger(UserActivityService.name);
constructor(
@InjectModel(UserActivity)
private userActivityModel: typeof UserActivity,
) { }
/**
* 记录用户活跃
*/
async recordActivity(userId: string, activityDto: CreateUserActivityDto): Promise<UserActivity> {
try {
// 使用 upsert 避免重复记录同一天同一类型的活跃
const [activity, created] = await this.userActivityModel.upsert({
userId,
...activityDto,
});
this.logger.log(`记录用户活跃 - 用户: ${userId}, 类型: ${activityDto.activityType}, 日期: ${activityDto.activityDate}, 是否新建: ${created}`);
return activity;
} catch (error) {
this.logger.error(`记录用户活跃失败: ${error.message}`, error.stack);
throw error;
}
}
/**
* 检查今日是否有登录记录,如果没有则创建
*/
async checkAndRecordTodayLogin(userId: string): Promise<void> {
const today = dayjs().format('YYYY-MM-DD'); // YYYY-MM-DD 格式
try {
const existingLogin = await this.userActivityModel.findOne({
where: {
userId,
activityDate: today,
activityType: ActivityType.LOGIN,
},
});
if (!existingLogin) {
await this.recordActivity(userId, {
activityType: ActivityType.LOGIN,
activityDate: today,
level: ActivityLevel.LOW, // 登录默认为低活跃
remark: '用户拉取profile接口自动记录',
});
}
} catch (error) {
this.logger.error(`检查并记录今日登录失败: ${error.message}`, error.stack);
// 不抛出错误,避免影响主要业务流程
}
}
/**
* 获取用户最近六个月的活跃情况
*/
async getUserActivityHistory(userId: string): Promise<UserActivitySummaryDto[]> {
const startDate = dayjs().subtract(6, 'month').format('YYYY-MM-DD');
const today = dayjs().format('YYYY-MM-DD');
try {
// 获取用户在指定时间范围内的活跃记录,按日期分组,取每日最高活跃等级
const activities = await this.userActivityModel.findAll({
where: {
userId,
activityDate: {
[Op.gte]: startDate,
[Op.lte]: today,
},
},
attributes: [
'activityDate',
[this.userActivityModel.sequelize!.fn('MAX', this.userActivityModel.sequelize!.col('level')), 'maxLevel'],
],
group: ['activityDate'],
order: [['activityDate', 'ASC']],
raw: true,
});
// 生成完整的日期范围最近6个月的每一天
const result: UserActivitySummaryDto[] = [];
let cursor = dayjs(startDate, 'YYYY-MM-DD');
const end = dayjs(today, 'YYYY-MM-DD');
while (!cursor.isAfter(end)) {
const dateStr = cursor.format('YYYY-MM-DD');
const activity = activities.find((a: any) => a.activityDate === dateStr);
result.push({
date: dateStr,
level: activity ? parseInt((activity as any).maxLevel) : ActivityLevel.NONE, // 无活跃
});
cursor = cursor.add(1, 'day');
}
this.logger.log(`获取用户活跃历史 - 用户: ${userId}, 记录数: ${result.length}`);
return result;
} catch (error) {
this.logger.error(`获取用户活跃历史失败: ${error.message}`, error.stack);
throw error;
}
}
/**
* 更新用户某日的活跃等级(如果新等级更高)
*/
async updateActivityLevel(userId: string, activityDate: string, activityType: ActivityType, newLevel: ActivityLevel): Promise<void> {
try {
const activity = await this.userActivityModel.findOne({
where: {
userId,
activityDate,
activityType,
},
});
if (activity && activity.level < newLevel) {
await activity.update({ level: newLevel });
this.logger.log(`更新用户活跃等级 - 用户: ${userId}, 日期: ${activityDate}, 类型: ${activityType}, 新等级: ${newLevel}`);
}
} catch (error) {
this.logger.error(`更新用户活跃等级失败: ${error.message}`, error.stack);
}
}
}