feat: add backend-mock app
This commit is contained in:
49
apps/backend-mock/src/modules/auth/auth.controller.ts
Normal file
49
apps/backend-mock/src/modules/auth/auth.controller.ts
Normal 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);
|
||||
}
|
||||
}
|
33
apps/backend-mock/src/modules/auth/auth.module.ts
Normal file
33
apps/backend-mock/src/modules/auth/auth.module.ts
Normal 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 {}
|
70
apps/backend-mock/src/modules/auth/auth.service.ts
Normal file
70
apps/backend-mock/src/modules/auth/auth.service.ts
Normal 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;
|
||||
}
|
||||
}
|
26
apps/backend-mock/src/modules/auth/jwt.strategy.ts
Normal file
26
apps/backend-mock/src/modules/auth/jwt.strategy.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
20
apps/backend-mock/src/modules/auth/local.strategy.ts
Normal file
20
apps/backend-mock/src/modules/auth/local.strategy.ts
Normal 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;
|
||||
}
|
||||
}
|
29
apps/backend-mock/src/modules/auth/refresh-token.strategy.ts
Normal file
29
apps/backend-mock/src/modules/auth/refresh-token.strategy.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
12
apps/backend-mock/src/modules/database/database.module.ts
Normal file
12
apps/backend-mock/src/modules/database/database.module.ts
Normal 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 {}
|
@@ -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();
|
||||
});
|
||||
});
|
40
apps/backend-mock/src/modules/database/database.service.ts
Normal file
40
apps/backend-mock/src/modules/database/database.service.ts
Normal 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);
|
||||
}
|
||||
}
|
11
apps/backend-mock/src/modules/health/health.controller.ts
Normal file
11
apps/backend-mock/src/modules/health/health.controller.ts
Normal 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';
|
||||
}
|
||||
}
|
8
apps/backend-mock/src/modules/health/health.module.ts
Normal file
8
apps/backend-mock/src/modules/health/health.module.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { HealthController } from './health.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [HealthController],
|
||||
})
|
||||
export class HealthModule {}
|
12
apps/backend-mock/src/modules/users/users.module.ts
Normal file
12
apps/backend-mock/src/modules/users/users.module.ts
Normal 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 {}
|
27
apps/backend-mock/src/modules/users/users.service.ts
Normal file
27
apps/backend-mock/src/modules/users/users.service.ts
Normal 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 } });
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user