feat: Prisma 用户落库、迁移与启动环境加载
- Prisma 7 + adapter-pg;prisma.config 与 users 初始迁移\n- AppModule 挂载 PrismaModule;PrismaService 仅依赖 DATABASE_URL\n- main 入口 dotenv/config,避免 Prisma 早于 Config 读 env\n- 短信登录 upsert User;默认昵称 Chat+手机号后四位\n- README / project-solution:目录、迁移规范、用户 avatar_url 说明\n- 依赖:dotenv、@prisma/adapter-pg、pg Made-with: Cursor
This commit is contained in:
@@ -4,6 +4,7 @@ import { LoggerModule } from 'nestjs-pino';
|
||||
import { ClientAppModule } from './apps/client-app/client-app.module';
|
||||
import { AdminAppModule } from './apps/admin-app/admin-app.module';
|
||||
import { RedisModule } from './apps/shared-domain/cache/redis.module';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import configuration from './config/configuration';
|
||||
import { validateEnv } from './config/validation';
|
||||
|
||||
@@ -25,6 +26,7 @@ import { validateEnv } from './config/validation';
|
||||
: undefined,
|
||||
},
|
||||
}),
|
||||
PrismaModule,
|
||||
RedisModule,
|
||||
ClientAppModule,
|
||||
AdminAppModule,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { JwtService } from '@nestjs/jwt';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { SmsService } from '@shared/sms/sms.service';
|
||||
import { RedisService } from '@shared/cache/redis.service';
|
||||
import { PrismaService } from '@prisma/prisma.service';
|
||||
|
||||
interface AccessPayload {
|
||||
sub: string;
|
||||
@@ -41,6 +42,8 @@ export class ClientAuthService {
|
||||
private readonly smsService: SmsService,
|
||||
@Inject(RedisService)
|
||||
private readonly redisService: RedisService,
|
||||
@Inject(PrismaService)
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
async sendSmsCode(phone: string, scene: string) {
|
||||
@@ -89,7 +92,21 @@ export class ClientAuthService {
|
||||
}
|
||||
await this.redisService.del(key);
|
||||
|
||||
const userId = `u_${phone}`;
|
||||
const user = await this.prisma.user.upsert({
|
||||
where: { phone },
|
||||
update: {
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
create: {
|
||||
phone,
|
||||
nickname: `Chat${phone.slice(-4)}`,
|
||||
},
|
||||
});
|
||||
if (user.status !== 1) {
|
||||
throw new UnauthorizedException('用户已被禁用');
|
||||
}
|
||||
|
||||
const userId = String(user.id);
|
||||
const accessToken = await this.signAccessToken({
|
||||
sub: userId,
|
||||
phone,
|
||||
@@ -108,6 +125,8 @@ export class ClientAuthService {
|
||||
user: {
|
||||
id: userId,
|
||||
phone,
|
||||
nickname: user.nickname ?? '',
|
||||
avatarUrl: user.avatarUrl ?? '',
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -126,9 +145,23 @@ export class ClientAuthService {
|
||||
throw new UnauthorizedException('token 类型错误');
|
||||
}
|
||||
|
||||
let userId: bigint;
|
||||
try {
|
||||
userId = BigInt(payload.sub);
|
||||
} catch {
|
||||
throw new UnauthorizedException('refreshToken 用户标识无效');
|
||||
}
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { id: true, phone: true, status: true },
|
||||
});
|
||||
if (!user || user.status !== 1) {
|
||||
throw new UnauthorizedException('用户不存在或已禁用');
|
||||
}
|
||||
|
||||
const accessToken = await this.signAccessToken({
|
||||
sub: payload.sub,
|
||||
phone: payload.sub.replace('u_', ''),
|
||||
sub: String(user.id),
|
||||
phone: user.phone,
|
||||
role: 'client',
|
||||
type: 'access',
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dotenv/config';
|
||||
import 'reflect-metadata';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import {
|
||||
INestApplication,
|
||||
Injectable,
|
||||
OnModuleDestroy,
|
||||
OnModuleInit,
|
||||
} from '@nestjs/common';
|
||||
// Prisma v7 默认导出 PrismaClient
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { PrismaPg } from '@prisma/adapter-pg';
|
||||
|
||||
@Injectable()
|
||||
export class PrismaService
|
||||
extends PrismaClient
|
||||
implements OnModuleInit, OnModuleDestroy
|
||||
{
|
||||
constructor() {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
if (!databaseUrl) {
|
||||
throw new Error('DATABASE_URL 未配置,Prisma 无法初始化');
|
||||
}
|
||||
const adapter = new PrismaPg({ connectionString: databaseUrl });
|
||||
super({
|
||||
adapter,
|
||||
});
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.$connect();
|
||||
}
|
||||
@@ -20,11 +25,5 @@ export class PrismaService
|
||||
async onModuleDestroy() {
|
||||
await this.$disconnect();
|
||||
}
|
||||
|
||||
async enableShutdownHooks(app: INestApplication) {
|
||||
this.$on('beforeExit', async () => {
|
||||
await app.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user