feat: add backend-mock app

This commit is contained in:
vben
2024-06-30 14:09:44 +08:00
parent c58aa26dbf
commit ca1cad0cd3
71 changed files with 3420 additions and 735 deletions

View File

@@ -0,0 +1,49 @@
import type { RefreshTokenDto } from '@/models/dto/auth.dto';
import { Public } from '@/core/decorator';
import { LocalAuthGuard } from '@/core/guard';
import {
Body,
Controller,
Get,
HttpCode,
HttpStatus,
Post,
Request,
UseGuards,
} from '@nestjs/common';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
/**
* 获取用户信息
* @param req
*/
@Get('getUserInfo')
@HttpCode(HttpStatus.OK)
async getProfile(@Request() req: Request) {
return await this.authService.getUserInfo(req.user.username);
}
/**
* 用户登录
* @param req
*/
@Public()
@UseGuards(LocalAuthGuard)
@Post('login')
@HttpCode(HttpStatus.OK)
async login(@Request() req: Request) {
return await this.authService.login(req.user);
}
@Post('refreshToken')
@HttpCode(HttpStatus.OK)
async refreshToken(@Body() refreshTokenDto: RefreshTokenDto) {
return this.authService.refresh(refreshTokenDto.refreshToken);
}
}

View File

@@ -0,0 +1,33 @@
import type { JwtConfig } from '@/types';
import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtModule } from '@nestjs/jwt';
import { UsersModule } from '../users/users.module';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './jwt.strategy';
import { LocalStrategy } from './local.strategy';
import { JwtRefreshStrategy } from './refresh-token.strategy';
@Module({
controllers: [AuthController],
exports: [AuthService],
imports: [
UsersModule,
JwtModule.registerAsync({
global: true,
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const { expiresIn, secret } = configService.get<JwtConfig>('jwt');
return {
secret,
signOptions: { expiresIn },
};
},
}),
],
providers: [AuthService, JwtStrategy, JwtRefreshStrategy, LocalStrategy],
})
export class AuthModule {}

View File

@@ -0,0 +1,70 @@
import type { UserEntity } from '@/models/entity/user.entity';
import type { JwtConfig } from '@/types';
import { UsersService } from '@/modules/users/users.service';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import bcrypt from 'bcryptjs';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
private configService: ConfigService,
) {}
/**
* get user info
* @param username
*/
async getUserInfo(username: string): Promise<Omit<UserEntity, 'password'>> {
const user = await this.usersService.findOne(username);
const { password: _pass, ...userInfo } = user;
return userInfo;
}
/**
* user login
*/
async login(userEntity: UserEntity): Promise<any> {
const { id, roles, username } = userEntity;
const payload = { id, roles, username };
const { refreshSecret, refreshexpiresIn } =
this.configService.get<JwtConfig>('jwt');
return {
accessToken: await this.jwtService.signAsync(payload),
refreshToken: this.jwtService.sign(payload, {
expiresIn: refreshexpiresIn,
secret: refreshSecret,
}),
};
}
async refresh(refreshToken: string) {
try {
const payload = this.jwtService.verify(refreshToken, {
secret: this.configService.get<JwtConfig>('jwt').refreshSecret,
});
const user = await this.usersService.findOne(payload.username);
if (!user) {
throw new UnauthorizedException();
}
return this.login(user);
} catch {
throw new UnauthorizedException();
}
}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.usersService.findOne(username);
if (user && (await bcrypt.compare(password, user.password))) {
// 使用 bcrypt.compare 验证密码
const { password: _pass, ...result } = user;
return result;
}
return null;
}
}

View File

@@ -0,0 +1,26 @@
import type { JwtConfig, JwtPayload } from '@/types';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
super({
ignoreExpiration: false,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get<JwtConfig>('jwt').secret,
});
}
async validate(payload: JwtPayload) {
console.log('jwt strategy validate payload', payload);
return {
id: payload.id,
roles: payload.roles,
username: payload.username,
};
}
}

View File

@@ -0,0 +1,20 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}

View File

@@ -0,0 +1,29 @@
import type { JwtConfig, JwtPayload } from '@/types';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
export class JwtRefreshStrategy extends PassportStrategy(
Strategy,
'jwt-refresh',
) {
constructor(configService: ConfigService) {
super({
ignoreExpiration: false,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configService.get<JwtConfig>('jwt').refreshSecret,
});
}
async validate(payload: JwtPayload) {
console.log('jwt refresh strategy validate payload', payload);
return {
id: payload.id,
roles: payload.roles,
username: payload.username,
};
}
}

View File

@@ -0,0 +1,12 @@
import { UserEntity } from '@/models/entity/user.entity';
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from '../users/users.module';
import { DatabaseService } from './database.service';
@Module({
imports: [UsersModule, TypeOrmModule.forFeature([UserEntity])],
providers: [DatabaseService],
})
export class DatabaseModule {}

View File

@@ -0,0 +1,19 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DatabaseService } from './database.service';
describe('databaseService', () => {
let service: DatabaseService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [DatabaseService],
}).compile();
service = module.get<DatabaseService>(DatabaseService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@@ -0,0 +1,40 @@
import type { Repository } from 'typeorm';
import { UserEntity } from '@/models/entity/user.entity';
import { Injectable, type OnModuleInit } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersService } from '../users/users.service';
@Injectable()
export class DatabaseService implements OnModuleInit {
constructor(
@InjectRepository(UserEntity)
private usersRepository: Repository<UserEntity>,
private userService: UsersService,
) {}
async onModuleInit() {
// data/db.sqlite会被git忽略方式数据库文件被提交到git
// 清空表,并初始化两条数据
await this.usersRepository.clear();
await this.userService.create({
id: 0,
password: '123456',
realName: 'Administrator',
roles: ['admin'],
username: 'vben',
});
await this.userService.create({
id: 1,
password: '123456',
realName: 'Jack',
roles: ['user'],
username: 'jack',
});
const count = await this.usersRepository.count();
console.log('Database has been initialized with seed data, count:', count);
}
}

View File

@@ -0,0 +1,11 @@
import { Public } from '@/core/decorator';
import { Controller, Get } from '@nestjs/common';
@Controller()
export class HealthController {
@Public()
@Get()
getHeart(): string {
return 'ok';
}
}

View File

@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { HealthController } from './health.controller';
@Module({
controllers: [HealthController],
})
export class HealthModule {}

View File

@@ -0,0 +1,12 @@
import { UserEntity } from '@/models/entity/user.entity';
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersService } from './users.service';
@Module({
exports: [UsersService],
imports: [TypeOrmModule.forFeature([UserEntity])],
providers: [UsersService],
})
export class UsersModule {}

View File

@@ -0,0 +1,27 @@
import type { Repository } from 'typeorm';
import { UserEntity } from '@/models/entity/user.entity';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import bcrypt from 'bcryptjs';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(UserEntity)
private usersRepository: Repository<UserEntity>,
) {}
async create(user: UserEntity): Promise<UserEntity> {
user.password = await bcrypt.hash(user.password, 10); // 密码哈希
return this.usersRepository.save(user);
}
/**
* Find user by username
* @param username
*/
async findOne(username: string): Promise<UserEntity | undefined> {
return await this.usersRepository.findOne({ where: { username } });
}
}