feat: 生成活动接口
This commit is contained in:
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"kiroAgent.configureMCP": "Enabled"
|
||||||
|
}
|
||||||
36
sql-scripts/user-activity-table-create.sql
Normal file
36
sql-scripts/user-activity-table-create.sql
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
-- 创建用户活跃记录表
|
||||||
|
CREATE TABLE IF NOT EXISTS `t_user_activities` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`userId` varchar(255) NOT NULL COMMENT '用户ID',
|
||||||
|
`activityType` tinyint NOT NULL COMMENT '活跃类型:1-登录,2-训练,3-饮食记录,4-体重记录,5-资料更新,6-打卡',
|
||||||
|
`activityDate` date NOT NULL COMMENT '活跃日期 YYYY-MM-DD',
|
||||||
|
`level` tinyint NOT NULL DEFAULT 1 COMMENT '活跃等级:0-无活跃,1-低活跃,2-中活跃,3-高活跃',
|
||||||
|
`remark` text COMMENT '备注信息',
|
||||||
|
`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `unique_user_activity_date_type` (`userId`, `activityDate`, `activityType`),
|
||||||
|
KEY `idx_user_activity_date` (`userId`, `activityDate`),
|
||||||
|
KEY `idx_activity_date` (`activityDate`),
|
||||||
|
-- 添加枚举约束
|
||||||
|
CONSTRAINT `chk_activity_type` CHECK (`activityType` IN (1, 2, 3, 4, 5, 6)),
|
||||||
|
CONSTRAINT `chk_activity_level` CHECK (`level` IN (0, 1, 2, 3))
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户活跃记录表';
|
||||||
|
|
||||||
|
-- 创建索引以优化查询性能
|
||||||
|
CREATE INDEX IF NOT EXISTS `idx_user_activity_level` ON `user_activities` (`userId`, `activityDate`, `level`);
|
||||||
|
|
||||||
|
-- 枚举值说明
|
||||||
|
-- activityType 枚举值:
|
||||||
|
-- 1: 登录 (LOGIN)
|
||||||
|
-- 2: 训练 (WORKOUT)
|
||||||
|
-- 3: 饮食记录 (DIET_RECORD)
|
||||||
|
-- 4: 体重记录 (WEIGHT_RECORD)
|
||||||
|
-- 5: 资料更新 (PROFILE_UPDATE)
|
||||||
|
-- 6: 打卡 (CHECKIN)
|
||||||
|
|
||||||
|
-- level 枚举值:
|
||||||
|
-- 0: 无活跃 (NONE)
|
||||||
|
-- 1: 低活跃 (LOW)
|
||||||
|
-- 2: 中活跃 (MEDIUM)
|
||||||
|
-- 3: 高活跃 (HIGH)
|
||||||
78
src/users/dto/user-activity.dto.ts
Normal file
78
src/users/dto/user-activity.dto.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsString, IsOptional, IsInt, Min, Max, IsDateString, IsEnum } from 'class-validator';
|
||||||
|
import { ResponseCode } from 'src/base.dto';
|
||||||
|
import { ActivityType, ActivityLevel } from '../models/user-activity.model';
|
||||||
|
|
||||||
|
export class CreateUserActivityDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: '活跃类型',
|
||||||
|
enum: ActivityType,
|
||||||
|
example: ActivityType.LOGIN
|
||||||
|
})
|
||||||
|
@IsEnum(ActivityType)
|
||||||
|
activityType!: ActivityType;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '活跃日期 YYYY-MM-DD', example: '2024-01-15' })
|
||||||
|
@IsDateString()
|
||||||
|
activityDate!: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '活跃等级',
|
||||||
|
enum: ActivityLevel,
|
||||||
|
example: ActivityLevel.LOW,
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 3
|
||||||
|
})
|
||||||
|
@IsEnum(ActivityLevel)
|
||||||
|
level!: ActivityLevel;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '备注', required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
remark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserActivityDto {
|
||||||
|
@ApiProperty({ description: '活跃记录ID' })
|
||||||
|
id!: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户ID' })
|
||||||
|
userId!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '活跃类型', enum: ActivityType })
|
||||||
|
activityType!: ActivityType;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '活跃日期' })
|
||||||
|
activityDate!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '活跃等级', enum: ActivityLevel })
|
||||||
|
level!: ActivityLevel;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '备注', required: false })
|
||||||
|
remark?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间' })
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间' })
|
||||||
|
updatedAt!: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserActivitySummaryDto {
|
||||||
|
@ApiProperty({ description: '日期 YYYY-MM-DD' })
|
||||||
|
date!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '活跃等级', enum: ActivityLevel })
|
||||||
|
level!: ActivityLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetUserActivityHistoryResponseDto {
|
||||||
|
@ApiProperty({ description: '响应码' })
|
||||||
|
code!: ResponseCode;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '响应消息' })
|
||||||
|
message!: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户活跃历史数据', type: [UserActivitySummaryDto] })
|
||||||
|
data!: UserActivitySummaryDto[];
|
||||||
|
}
|
||||||
108
src/users/models/user-activity.model.ts
Normal file
108
src/users/models/user-activity.model.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { DataTypes, Model, Optional } from 'sequelize';
|
||||||
|
import { Column, Table, Model as SequelizeModel, DataType, ForeignKey, BelongsTo } from 'sequelize-typescript';
|
||||||
|
import { User } from './user.model';
|
||||||
|
|
||||||
|
export enum ActivityType {
|
||||||
|
LOGIN = 1, // 登录
|
||||||
|
WORKOUT = 2, // 训练
|
||||||
|
DIET_RECORD = 3, // 饮食记录
|
||||||
|
WEIGHT_RECORD = 4, // 体重记录
|
||||||
|
PROFILE_UPDATE = 5, // 资料更新
|
||||||
|
CHECKIN = 6, // 打卡
|
||||||
|
}
|
||||||
|
|
||||||
|
// 活跃类型显示名称映射
|
||||||
|
export const ActivityTypeNames: Record<ActivityType, string> = {
|
||||||
|
[ActivityType.LOGIN]: '登录',
|
||||||
|
[ActivityType.WORKOUT]: '训练',
|
||||||
|
[ActivityType.DIET_RECORD]: '饮食记录',
|
||||||
|
[ActivityType.WEIGHT_RECORD]: '体重记录',
|
||||||
|
[ActivityType.PROFILE_UPDATE]: '资料更新',
|
||||||
|
[ActivityType.CHECKIN]: '打卡',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取活跃类型显示名称的辅助函数
|
||||||
|
export function getActivityTypeName(activityType: ActivityType): string {
|
||||||
|
return ActivityTypeNames[activityType] || '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ActivityLevel {
|
||||||
|
NONE = 0, // 无活跃
|
||||||
|
LOW = 1, // 低活跃
|
||||||
|
MEDIUM = 2, // 中活跃
|
||||||
|
HIGH = 3, // 高活跃
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserActivityAttributes {
|
||||||
|
id: number;
|
||||||
|
userId: string;
|
||||||
|
activityType: ActivityType;
|
||||||
|
activityDate: string; // YYYY-MM-DD 格式
|
||||||
|
level: ActivityLevel; // 活跃等级
|
||||||
|
remark?: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserActivityCreationAttributes extends Optional<UserActivityAttributes, 'id' | 'createdAt' | 'updatedAt' | 'remark'> { }
|
||||||
|
|
||||||
|
@Table({
|
||||||
|
tableName: 'user_activities',
|
||||||
|
timestamps: true,
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
unique: true,
|
||||||
|
fields: ['userId', 'activityDate', 'activityType']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fields: ['userId', 'activityDate']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class UserActivity extends SequelizeModel<UserActivityAttributes, UserActivityCreationAttributes> {
|
||||||
|
@Column({
|
||||||
|
type: DataType.INTEGER,
|
||||||
|
primaryKey: true,
|
||||||
|
autoIncrement: true,
|
||||||
|
})
|
||||||
|
declare id: number;
|
||||||
|
|
||||||
|
@ForeignKey(() => User)
|
||||||
|
@Column({
|
||||||
|
type: DataType.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
})
|
||||||
|
userId!: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: DataType.TINYINT,
|
||||||
|
allowNull: false,
|
||||||
|
comment: '活跃类型:1-登录,2-训练,3-饮食记录,4-体重记录,5-资料更新,6-打卡',
|
||||||
|
})
|
||||||
|
activityType!: ActivityType;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: DataType.DATEONLY,
|
||||||
|
allowNull: false,
|
||||||
|
comment: '活跃日期 YYYY-MM-DD',
|
||||||
|
})
|
||||||
|
activityDate!: string;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: DataType.TINYINT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: ActivityLevel.LOW,
|
||||||
|
comment: '活跃等级:0-无活跃,1-低活跃,2-中活跃,3-高活跃',
|
||||||
|
})
|
||||||
|
level!: ActivityLevel;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: DataType.TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
comment: '备注信息',
|
||||||
|
})
|
||||||
|
remark?: string;
|
||||||
|
|
||||||
|
@BelongsTo(() => User)
|
||||||
|
user!: User;
|
||||||
|
}
|
||||||
137
src/users/services/user-activity.service.ts
Normal file
137
src/users/services/user-activity.service.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import { CreateDietRecordDto, UpdateDietRecordDto, GetDietHistoryQueryDto, DietR
|
|||||||
import { GuestLoginDto, GuestLoginResponseDto, RefreshGuestTokenDto, RefreshGuestTokenResponseDto } from './dto/guest-login.dto';
|
import { GuestLoginDto, GuestLoginResponseDto, RefreshGuestTokenDto, RefreshGuestTokenResponseDto } from './dto/guest-login.dto';
|
||||||
import { AppStoreServerNotificationDto, ProcessNotificationResponseDto } from './dto/app-store-notification.dto';
|
import { AppStoreServerNotificationDto, ProcessNotificationResponseDto } from './dto/app-store-notification.dto';
|
||||||
import { RestorePurchaseDto, RestorePurchaseResponseDto } from './dto/restore-purchase.dto';
|
import { RestorePurchaseDto, RestorePurchaseResponseDto } from './dto/restore-purchase.dto';
|
||||||
|
import { GetUserActivityHistoryResponseDto } from './dto/user-activity.dto';
|
||||||
import { Public } from '../common/decorators/public.decorator';
|
import { Public } from '../common/decorators/public.decorator';
|
||||||
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
import { CurrentUser } from '../common/decorators/current-user.decorator';
|
||||||
import { AccessTokenPayload } from './services/apple-auth.service';
|
import { AccessTokenPayload } from './services/apple-auth.service';
|
||||||
@@ -317,4 +318,21 @@ export class UsersController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// ==================== 用户活跃记录相关接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户最近六个月的活跃情况
|
||||||
|
*/
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
@Get('activity-history')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@ApiOperation({ summary: '获取用户最近六个月的活跃情况' })
|
||||||
|
@ApiResponse({ status: 200, description: '成功获取用户活跃历史', type: GetUserActivityHistoryResponseDto })
|
||||||
|
async getUserActivityHistory(
|
||||||
|
@CurrentUser() user: AccessTokenPayload,
|
||||||
|
): Promise<GetUserActivityHistoryResponseDto> {
|
||||||
|
this.logger.log(`获取用户活跃历史 - 用户ID: ${user.sub}`);
|
||||||
|
return this.usersService.getUserActivityHistory(user.sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { UserProfile } from "./models/user-profile.model";
|
|||||||
import { UserWeightHistory } from "./models/user-weight-history.model";
|
import { UserWeightHistory } from "./models/user-weight-history.model";
|
||||||
import { UserDietHistory } from "./models/user-diet-history.model";
|
import { UserDietHistory } from "./models/user-diet-history.model";
|
||||||
import { ApplePurchaseService } from "./services/apple-purchase.service";
|
import { ApplePurchaseService } from "./services/apple-purchase.service";
|
||||||
|
import { UserActivity } from "./models/user-activity.model";
|
||||||
|
import { UserActivityService } from "./services/user-activity.service";
|
||||||
import { EncryptionService } from "../common/encryption.service";
|
import { EncryptionService } from "../common/encryption.service";
|
||||||
import { AppleAuthService } from "./services/apple-auth.service";
|
import { AppleAuthService } from "./services/apple-auth.service";
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
@@ -28,6 +30,7 @@ import { ActivityLogsModule } from '../activity-logs/activity-logs.module';
|
|||||||
UserProfile,
|
UserProfile,
|
||||||
UserWeightHistory,
|
UserWeightHistory,
|
||||||
UserDietHistory,
|
UserDietHistory,
|
||||||
|
UserActivity,
|
||||||
]),
|
]),
|
||||||
forwardRef(() => ActivityLogsModule),
|
forwardRef(() => ActivityLogsModule),
|
||||||
JwtModule.register({
|
JwtModule.register({
|
||||||
@@ -36,7 +39,7 @@ import { ActivityLogsModule } from '../activity-logs/activity-logs.module';
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
controllers: [UsersController],
|
controllers: [UsersController],
|
||||||
providers: [UsersService, ApplePurchaseService, EncryptionService, AppleAuthService, CosService],
|
providers: [UsersService, ApplePurchaseService, EncryptionService, AppleAuthService, CosService, UserActivityService],
|
||||||
exports: [UsersService, AppleAuthService],
|
exports: [UsersService, AppleAuthService],
|
||||||
})
|
})
|
||||||
export class UsersModule { }
|
export class UsersModule { }
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import { BlockedTransaction, BlockReason } from './models/blocked-transaction.mo
|
|||||||
import { UserWeightHistory, WeightUpdateSource } from './models/user-weight-history.model';
|
import { UserWeightHistory, WeightUpdateSource } from './models/user-weight-history.model';
|
||||||
import { UserDietHistory, DietRecordSource, MealType } from './models/user-diet-history.model';
|
import { UserDietHistory, DietRecordSource, MealType } from './models/user-diet-history.model';
|
||||||
import { ActivityLogsService } from '../activity-logs/activity-logs.service';
|
import { ActivityLogsService } from '../activity-logs/activity-logs.service';
|
||||||
|
import { UserActivityService } from './services/user-activity.service';
|
||||||
|
import { GetUserActivityHistoryResponseDto } from './dto/user-activity.dto';
|
||||||
import { ActivityActionType, ActivityEntityType } from '../activity-logs/models/activity-log.model';
|
import { ActivityActionType, ActivityEntityType } from '../activity-logs/models/activity-log.model';
|
||||||
import { CreateDietRecordDto, UpdateDietRecordDto, GetDietHistoryQueryDto, DietRecordResponseDto, DietHistoryResponseDto, NutritionSummaryDto } from './dto/diet-record.dto';
|
import { CreateDietRecordDto, UpdateDietRecordDto, GetDietHistoryQueryDto, DietRecordResponseDto, DietHistoryResponseDto, NutritionSummaryDto } from './dto/diet-record.dto';
|
||||||
|
|
||||||
@@ -64,6 +66,7 @@ export class UsersService {
|
|||||||
@InjectConnection()
|
@InjectConnection()
|
||||||
private sequelize: Sequelize,
|
private sequelize: Sequelize,
|
||||||
private readonly activityLogsService: ActivityLogsService,
|
private readonly activityLogsService: ActivityLogsService,
|
||||||
|
private readonly userActivityService: UserActivityService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async getProfile(user: AccessTokenPayload): Promise<UserResponseDto> {
|
async getProfile(user: AccessTokenPayload): Promise<UserResponseDto> {
|
||||||
@@ -94,6 +97,9 @@ export class UsersService {
|
|||||||
where: { userId: existingUser.id },
|
where: { userId: existingUser.id },
|
||||||
defaults: { userId: existingUser.id },
|
defaults: { userId: existingUser.id },
|
||||||
});
|
});
|
||||||
|
// 检查并记录今日登录活跃
|
||||||
|
await this.userActivityService.checkAndRecordTodayLogin(existingUser.id);
|
||||||
|
|
||||||
const returnData = {
|
const returnData = {
|
||||||
...existingUser.toJSON(),
|
...existingUser.toJSON(),
|
||||||
maxUsageCount: DEFAULT_FREE_USAGE_COUNT,
|
maxUsageCount: DEFAULT_FREE_USAGE_COUNT,
|
||||||
@@ -2334,4 +2340,26 @@ export class UsersService {
|
|||||||
this.logger.error(`关联 RevenueCat 用户失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
this.logger.error(`关联 RevenueCat 用户失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户最近六个月的活跃情况
|
||||||
|
*/
|
||||||
|
async getUserActivityHistory(userId: string): Promise<GetUserActivityHistoryResponseDto> {
|
||||||
|
try {
|
||||||
|
const activityHistory = await this.userActivityService.getUserActivityHistory(userId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: ResponseCode.SUCCESS,
|
||||||
|
message: 'success',
|
||||||
|
data: activityHistory,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`获取用户活跃历史失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||||
|
return {
|
||||||
|
code: ResponseCode.ERROR,
|
||||||
|
message: `获取用户活跃历史失败: ${error instanceof Error ? error.message : '未知错误'}`,
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user