feat: Phase 1-3 全部完成 — 沙盒增强、学情分析、学习路径

This commit is contained in:
yuzhiran-dev
2026-05-18 09:48:51 +08:00
commit 11bb86854c
277 changed files with 37755 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcryptjs';
import { PrismaService } from '../../prisma/prisma.service';
@Injectable()
export class AuthService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService,
) {}
async register(data: { phone?: string; email?: string; password: string; nickname?: string }) {
const passwordHash = await bcrypt.hash(data.password, 10);
const user = await this.prisma.user.create({
data: {
phone: data.phone,
email: data.email,
passwordHash,
nickname: data.nickname || data.phone || data.email?.split('@')[0],
},
});
return this.generateTokens(user.id);
}
async login(account: string, password: string) {
const user = await this.prisma.user.findFirst({
where: {
OR: [{ phone: account }, { email: account }],
deletedAt: null,
},
});
if (!user || !user.passwordHash) {
throw new UnauthorizedException('账号或密码错误');
}
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) {
throw new UnauthorizedException('账号或密码错误');
}
await this.prisma.user.update({
where: { id: user.id },
data: { lastLoginAt: new Date() },
});
return this.generateTokens(user.id);
}
async refreshAccessToken(accessToken: string) {
try {
const payload = this.jwtService.verify(accessToken, { ignoreExpiration: true });
const user = await this.prisma.user.findUnique({
where: { id: payload.sub, deletedAt: null },
});
if (!user || user.status !== 'ACTIVE') {
throw new UnauthorizedException('用户不可用');
}
return this.generateTokens(user.id);
} catch {
throw new UnauthorizedException('Token 无效');
}
}
async getProfile(userId: number) {
return this.prisma.user.findUnique({
where: { id: userId },
select: {
id: true,
phone: true,
email: true,
nickname: true,
avatar: true,
status: true,
memberPlan: true,
memberExpire: true,
sandboxDaily: true,
createdAt: true,
},
});
}
private generateTokens(userId: number) {
const payload = { sub: userId };
return {
accessToken: this.jwtService.sign(payload, { expiresIn: '2h' }),
refreshToken: this.jwtService.sign(payload, { expiresIn: '7d' }),
};
}
}