feat: commit

This commit is contained in:
luchang 2024-06-19 14:17:37 +08:00
parent fa41ee8a4b
commit 24ead9aad7
31 changed files with 4115 additions and 4516 deletions

22
config/configuration.ts Normal file
View File

@ -0,0 +1,22 @@
import * as fs from 'fs-extra';
import * as yaml from 'js-yaml';
import * as path from 'path';
const ENV_NAME = {
development: 'dev',
production: 'prod',
};
const env = process.env.NODE_ENV || 'development';
export function getConfig() {
const filePath = path.join(__dirname, './', `${ENV_NAME[env]}.yaml`);
if (!fs.existsSync(filePath)) {
throw new Error(`Can not find config file: ${filePath}`);
}
return yaml.load(fs.readFileSync(filePath, 'utf-8')) as Record<
string,
unknown
>;
}

25
config/dev.yaml Normal file
View File

@ -0,0 +1,25 @@
server:
port: 4399
prefix: ""
# 数据库配置
db:
mysql:
host: "localhost"
port: 3306
username: "root"
password: "123456"
database: "travel_app"
synchronize: true
# Jwt配置
jwt:
secret: "travel_app"
expiresIn: "2h"
# Oss配置
oss:
accessKeyId: "LTAI5tHfUhFjJ335EVo1vcWm"
accessKeySecret: "0q5JF4862ai5pznfIZMTIjThVjzaqK"
bucket: "polaris-frontend"
dir: "yitu_image/"

View File

@ -1,9 +0,0 @@
// 本地运行是没有 process.env.NODE_ENV 的,借此来区分[开发环境]和[生产环境]
const ossConfig = {
accessKeyId: 'LTAI5tHfUhFjJ335EVo1vcWm',
accessKeySecret: '0q5JF4862ai5pznfIZMTIjThVjzaqK',
bucket: 'polaris-frontend',
dir: 'yitu_image/',
};
export default ossConfig;

25
config/prod.yaml Normal file
View File

@ -0,0 +1,25 @@
server:
port: 4399
prefix: ""
# 数据库配置
db:
mysql:
host: "rm-uf686x3ni1tiz68z088270.mysql.rds.aliyuncs.com"
port: 3306
username: "travel_app"
password: "travel_app123"
database: "travel_app"
synchronize: true
# Jwt配置
jwt:
secret: "travel_app"
expiresIn: "2h"
# Oss配置
oss:
accessKeyId: "LTAI5tHfUhFjJ335EVo1vcWm"
accessKeySecret: "0q5JF4862ai5pznfIZMTIjThVjzaqK"
bucket: "polaris-frontend"
dir: "yitu_image/"

View File

@ -3,6 +3,7 @@
"collection": "@nestjs/schematics", "collection": "@nestjs/schematics",
"sourceRoot": "src", "sourceRoot": "src",
"compilerOptions": { "compilerOptions": {
"deleteOutDir": true "deleteOutDir": true,
"assets": [{"include": "../config/*.yaml", "outDir": "./dist/config"}]
} }
} }

View File

@ -6,7 +6,7 @@
"private": true, "private": true,
"license": "UNLICENSED", "license": "UNLICENSED",
"scripts": { "scripts": {
"build": "nest build", "build": "NODE_ENV=production nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start", "start": "nest start",
"start:dev": "nest start --watch", "start:dev": "nest start --watch",
@ -21,16 +21,25 @@
}, },
"dependencies": { "dependencies": {
"@nestjs/common": "^10.0.0", "@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.2",
"@nestjs/core": "^10.0.0", "@nestjs/core": "^10.0.0",
"@nestjs/jwt": "^10.2.0",
"@nestjs/mapped-types": "*", "@nestjs/mapped-types": "*",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0", "@nestjs/platform-express": "^10.0.0",
"@nestjs/typeorm": "^10.0.2", "@nestjs/typeorm": "^10.0.2",
"@types/ali-oss": "^6.16.11", "@types/ali-oss": "^6.16.11",
"ali-oss": "^6.20.0", "ali-oss": "^6.20.0",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"dayjs": "^1.11.11", "dayjs": "^1.11.11",
"fs": "0.0.1-security",
"fs-extra": "^11.2.0",
"js-yaml": "^4.1.0",
"multer": "1.4.5-lts.1", "multer": "1.4.5-lts.1",
"mysql2": "^3.9.7", "mysql2": "^3.9.7",
"passport-jwt": "^4.0.1",
"reflect-metadata": "^0.2.0", "reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"typeorm": "^0.3.20" "typeorm": "^0.3.20"
@ -41,7 +50,9 @@
"@nestjs/testing": "^10.0.0", "@nestjs/testing": "^10.0.0",
"@types/express": "^4.17.17", "@types/express": "^4.17.17",
"@types/jest": "^29.5.2", "@types/jest": "^29.5.2",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.3.1", "@types/node": "^20.3.1",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^6.0.0", "@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0", "@typescript-eslint/parser": "^6.0.0",

8093
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -3,32 +3,48 @@ import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { UserEntity } from './user/entities/user.entity'; import { UserEntity } from './user/entities/user.entity';
import { BannerEntity } from './banner/entities/banner.entity'; import { BannerEntity } from './banner/entities/banner.entity';
import { AuthEntity } from './auth/entities/auth.entity';
import { UserModule } from './user/user.module'; import { UserModule } from './user/user.module';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule, TypeOrmModuleOptions } from '@nestjs/typeorm';
import { BannerModule } from './banner/banner.module'; import { BannerModule } from './banner/banner.module';
import { OssModule } from './oss/oss.module'; import { OssModule } from './oss/oss.module';
import { SysUserModule } from './sys-user/sys-user.module'; import { AuthModule } from './auth/auth.module';
import { APP_GUARD } from '@nestjs/core';
import { jwtAuthGuard } from './jwt/jwt-auth.grard';
import { getConfig } from 'config/configuration';
import { ConfigModule, ConfigService } from '@nestjs/config';
const ENTITIES = [UserEntity, BannerEntity]; const ENTITIES = [UserEntity, BannerEntity, AuthEntity];
const MODULE = [UserModule, BannerModule, OssModule]; const MODULE = [UserModule, BannerModule, OssModule, AuthModule];
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forRoot({ ConfigModule.forRoot({
cache: true,
load: [getConfig],
isGlobal: true,
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => {
return {
type: 'mysql', type: 'mysql',
host: 'localhost', entities: ENTITIES,
port: 3306, ...config.get('db.mysql'),
username: 'root', } as TypeOrmModuleOptions;
password: '123456', },
database: 'travel-app',
entities: ENTITIES, // 这里添加实体类
synchronize: true,
}), }),
...MODULE, ...MODULE,
SysUserModule,
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [
AppService,
{
provide: APP_GUARD,
useClass: jwtAuthGuard,
},
],
}) })
export class AppModule {} export class AppModule {}

View File

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

View File

@ -0,0 +1,37 @@
import {
Controller,
Get,
Post,
Body,
Request,
UseGuards,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { CreateAuthDto } from './dto/create-auth.dto';
import { Public } from 'src/common/public.decorator';
import { jwtAuthGuard } from 'src/jwt/jwt-auth.grard';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
// 注册
@Public()
@Post('/signup')
signup(@Body() signupData: CreateAuthDto) {
console.log('>>>>>signupData', signupData);
return this.authService.signup(signupData);
}
// 登录
@Public()
@Post('/login')
login(@Body() loginData: CreateAuthDto) {
return this.authService.login(loginData);
}
@UseGuards(jwtAuthGuard)
@Get('/user')
getuserInfo(@Request() req) {
return this.authService.getUserInfo(req.user.id);
}
}

25
src/auth/auth.module.ts Normal file
View File

@ -0,0 +1,25 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { AuthEntity } from './entities/auth.entity';
import { TypeOrmModule } from '@nestjs/typeorm';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import JwtAuthStrategy from 'src/jwt/jwt-auth.strategy';
@Module({
imports: [
TypeOrmModule.forFeature([AuthEntity]),
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get('jwt.secret'),
signOptions: { expiresIn: configService.get('jwt.expiresIn') },
}),
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [AuthService, JwtAuthStrategy],
})
export class AuthModule {}

View File

@ -1,15 +1,15 @@
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { SysUserService } from './sys-user.service'; import { AuthService } from './auth.service';
describe('SysUserService', () => { describe('AuthService', () => {
let service: SysUserService; let service: AuthService;
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
providers: [SysUserService], providers: [AuthService],
}).compile(); }).compile();
service = module.get<SysUserService>(SysUserService); service = module.get<AuthService>(AuthService);
}); });
it('should be defined', () => { it('should be defined', () => {

55
src/auth/auth.service.ts Normal file
View File

@ -0,0 +1,55 @@
import { Injectable } from '@nestjs/common';
import { CreateAuthDto } from './dto/create-auth.dto';
import { AuthEntity } from './entities/auth.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { JwtService } from '@nestjs/jwt';
import * as bcryptjs from 'bcryptjs';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(AuthEntity) private readonly user: Repository<AuthEntity>,
private readonly jwtService: JwtService,
) {}
async signup(signupData: CreateAuthDto) {
const findUser = await this.user.findOne({
where: { username: signupData.username },
});
if (findUser && findUser.username === signupData.username)
return '用户已注册';
signupData.password = bcryptjs.hashSync(signupData.password, 10);
this.user.save(signupData);
return '注册成功';
}
async login(loginData: CreateAuthDto) {
const findUser = await this.user.findOne({
where: { username: loginData.username },
});
if (!findUser) return '用户不存在';
const compareRes: boolean = bcryptjs.compareSync(
loginData.password,
findUser.password,
);
if (!compareRes) return { msg: '账号或者密码不正确' };
const payload = { id: findUser.id, username: findUser.username };
return {
token: this.jwtService.sign(payload),
msg: '登录成功',
};
}
async getUserInfo(userId: number) {
const { password, ...result } = await this.user.findOne({
where: { id: userId },
});
return result;
}
}

View File

@ -0,0 +1,14 @@
import { IsString, IsNotEmpty, MinLength } from 'class-validator';
export class CreateAuthDto {
@IsString()
@IsNotEmpty()
username: string;
@IsString()
@IsNotEmpty()
@MinLength(6, {
message: '密码不能少于六位',
})
password: string;
}

View File

@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateAuthDto } from './create-auth.dto';
export class UpdateAuthDto extends PartialType(CreateAuthDto) {}

View File

@ -0,0 +1,19 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class AuthEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
@Column({
default:
'https://polaris-frontend.oss-cn-shanghai.aliyuncs.com/yitu_image/1717148513267default_user.png',
})
avatar: string;
}

View File

@ -0,0 +1,4 @@
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

View File

@ -3,6 +3,7 @@ import {
Catch, Catch,
ArgumentsHost, ArgumentsHost,
HttpException, HttpException,
HttpStatus,
} from '@nestjs/common'; } from '@nestjs/common';
import { Response } from 'express'; import { Response } from 'express';
@ -11,11 +12,18 @@ export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) { catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp(); const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>(); const response = ctx.getResponse<Response>();
const status = exception.getStatus(); const status = exception.getStatus
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const exceptionResponse = exception.getResponse() as { message };
const errorMessage = exceptionResponse.message
? exceptionResponse.message
: exception.message;
response.status(status).json({ response.status(status).json({
code: 1, code: status,
message: '服务错误', message: Array.isArray(errorMessage)
? errorMessage.join(',')
: errorMessage,
data: null, data: null,
}); });
} }

23
src/jwt/jwt-auth.grard.ts Normal file
View File

@ -0,0 +1,23 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { IS_PUBLIC_KEY } from 'src/common/public.decorator';
@Injectable()
export class jwtAuthGuard extends AuthGuard('jwt') {
constructor(private reflector: Reflector) {
super();
}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
return super.canActivate(context);
}
}

View File

@ -0,0 +1,29 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
export interface JwtPayload {
id: number;
username: string;
}
@Injectable()
// 验证请求头中的token
export default class JwtAuthStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(private readonly configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('jwt.secret'),
});
}
async validate(payload: JwtPayload) {
const { username, id } = payload;
return {
id,
username,
};
}
}

View File

@ -1,3 +1,4 @@
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { HttpExceptionFilter } from './http-exception/http-exception.filter'; import { HttpExceptionFilter } from './http-exception/http-exception.filter';
@ -6,11 +7,16 @@ import { TransformInterceptor } from './transoform/transform.interceptor';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
// 设置全局路由前缀,例如 'api' // 设置全局路由前缀,例如 'api'
app.setGlobalPrefix('api');
app.enableCors(); app.enableCors();
app.useGlobalPipes(
new ValidationPipe({
transform: true,
whitelist: true,
}),
);
app.useGlobalFilters(new HttpExceptionFilter()); app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalInterceptors(new TransformInterceptor()); app.useGlobalInterceptors(new TransformInterceptor());
// 如果有全局拦截器,确保添加它 // 如果有全局拦截器,确保添加它
await app.listen(3000); await app.listen(4399);
} }
bootstrap(); bootstrap();

View File

@ -1,8 +1,10 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { OssService } from './oss.service'; import { OssService } from './oss.service';
import { OssController } from './oss.controller'; import { OssController } from './oss.controller';
import { ConfigModule } from '@nestjs/config';
@Module({ @Module({
imports: [ConfigModule],
controllers: [OssController], controllers: [OssController],
providers: [OssService], providers: [OssService],
}) })

View File

@ -1,11 +1,13 @@
import * as OSS from 'ali-oss'; import * as OSS from 'ali-oss';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import ossConfig from '../../config/oss';
import * as dayjs from 'dayjs'; import * as dayjs from 'dayjs';
import { ConfigService } from '@nestjs/config';
@Injectable() @Injectable()
export class OssService { export class OssService {
constructor(private configService: ConfigService) {}
async getSignature() { async getSignature() {
const ossConfig = this.configService.get('oss');
const config = { const config = {
// 填写你自己的 AccessKey // 填写你自己的 AccessKey
accessKeyId: ossConfig.accessKeyId, accessKeyId: ossConfig.accessKeyId,

View File

@ -1 +0,0 @@
export class CreateSysUserDto {}

View File

@ -1,4 +0,0 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateSysUserDto } from './create-sys-user.dto';
export class UpdateSysUserDto extends PartialType(CreateSysUserDto) {}

View File

@ -1,13 +0,0 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class SysUserEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
password: string;
}

View File

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

View File

@ -1,42 +0,0 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { SysUserService } from './sys-user.service';
import { CreateSysUserDto } from './dto/create-sys-user.dto';
import { UpdateSysUserDto } from './dto/update-sys-user.dto';
@Controller('sys-user')
export class SysUserController {
constructor(private readonly sysUserService: SysUserService) {}
@Post()
create(@Body() createSysUserDto: CreateSysUserDto) {
return this.sysUserService.create(createSysUserDto);
}
@Get()
findAll() {
return this.sysUserService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.sysUserService.findOne(+id);
}
@Patch(':id')
update(@Param('id') id: string, @Body() updateSysUserDto: UpdateSysUserDto) {
return this.sysUserService.update(+id, updateSysUserDto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.sysUserService.remove(+id);
}
}

View File

@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { SysUserService } from './sys-user.service';
import { SysUserController } from './sys-user.controller';
@Module({
controllers: [SysUserController],
providers: [SysUserService],
})
export class SysUserModule {}

View File

@ -1,26 +0,0 @@
import { Injectable } from '@nestjs/common';
import { CreateSysUserDto } from './dto/create-sys-user.dto';
import { UpdateSysUserDto } from './dto/update-sys-user.dto';
@Injectable()
export class SysUserService {
create(createSysUserDto: CreateSysUserDto) {
return 'This action adds a new sysUser';
}
findAll() {
return `This action returns all sysUser`;
}
findOne(id: number) {
return `This action returns a #${id} sysUser`;
}
update(id: number, updateSysUserDto: UpdateSysUserDto) {
return `This action updates a #${id} sysUser`;
}
remove(id: number) {
return `This action removes a #${id} sysUser`;
}
}

View File

@ -6,7 +6,7 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { classToPlain } from 'class-transformer'; import { instanceToPlain } from 'class-transformer';
@Injectable() @Injectable()
export class TransformInterceptor implements NestInterceptor { export class TransformInterceptor implements NestInterceptor {
@ -15,8 +15,8 @@ export class TransformInterceptor implements NestInterceptor {
map((data) => { map((data) => {
return { return {
code: 0, code: 0,
data: classToPlain(data), data: instanceToPlain(data),
message: null, message: 'ok',
}; };
}), }),
); );