瀏覽代碼

feat: save

gemercheung 7 月之前
父節點
當前提交
f41eeed34c

+ 1 - 0
packages/backend/.gitignore

@@ -3,6 +3,7 @@
 /ncc-dist
 /node_modules
 
+helper-log
 # Logs
 logs
 *.log

+ 2 - 0
packages/backend/package.json

@@ -38,6 +38,7 @@
     "pino": "^9.6.0",
     "redis": "^4.6.11",
     "reflect-metadata": "^0.1.13",
+    "request-ip": "^3.3.0",
     "rxjs": "^7.8.1",
     "svg-captcha": "^1.4.0",
     "typeorm": "^0.3.17",
@@ -53,6 +54,7 @@
     "@types/jest": "^29.5.2",
     "@types/multer": "^1.4.12",
     "@types/node": "^20.3.1",
+    "@types/request-ip": "^0.0.41",
     "@types/supertest": "^2.0.12",
     "@typescript-eslint/eslint-plugin": "^6.0.0",
     "@typescript-eslint/parser": "^6.0.0",

+ 24 - 3
packages/backend/src/app.module.ts

@@ -1,4 +1,5 @@
-import { Module } from '@nestjs/common';
+import pino from 'pino';
+import { Module, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
 import { SharedModule } from './shared/shared.module';
 import { ConfigModule } from '@nestjs/config';
 import { UserModule } from './modules/user/user.module';
@@ -9,9 +10,20 @@ import { ArticleModule } from './modules/article/article.module';
 import { CategoryModule } from './modules/category/category.module';
 import { LoggerModule } from 'nestjs-pino';
 import { MenuModule } from './modules/menu/menu.module';
+import { HttpLoggerMiddleware } from './common/middleware/httpOperationMiddleware';
+import { OperationLogModule } from './modules/operation-log/operation-log.module';
+import { OperationLogService } from './modules/operation-log/operation-log.service';
 @Module({
   imports: [
-    LoggerModule.forRoot(),
+    LoggerModule.forRoot({
+      pinoHttp: {
+        stream: pino.destination({
+          dest: './helper-log', // omit for stdout
+          minLength: 4096, // Buffer before writing
+          sync: true, // Asynchronous logging
+        }),
+      },
+    }),
     /* 配置文件模块 */
     ConfigModule.forRoot({
       isGlobal: true,
@@ -30,8 +42,17 @@ import { MenuModule } from './modules/menu/menu.module';
     CategoryModule,
 
     MenuModule,
+
+    OperationLogModule,
   ],
   controllers: [],
   providers: [],
 })
-export class AppModule {}
+export class AppModule {
+  configure(consumer: MiddlewareConsumer) {
+    consumer.apply(HttpLoggerMiddleware).forRoutes({
+      path: '*',
+      method: RequestMethod.ALL,
+    });
+  }
+}

+ 26 - 0
packages/backend/src/common/middleware/httpOperationMiddleware.ts

@@ -0,0 +1,26 @@
+import { MenuService } from '@/modules/menu/menu.service';
+import { OperationLogService } from '@/modules/operation-log/operation-log.service';
+import { Inject, Injectable, Logger, NestMiddleware } from '@nestjs/common';
+import { NextFunction, Request, Response } from 'express';
+
+@Injectable()
+export class HttpLoggerMiddleware implements NestMiddleware {
+  constructor(
+    // @Inject(OperationLogService) private readonly operationLogService: OperationLogService,
+  ) {}
+
+  private logger = new Logger();
+
+  use(request: Request, response: Response, next: NextFunction): void {
+    const { ip, method, originalUrl } = request;
+    const ips = request.ip || request.connection.remoteAddress;
+
+    response.on('finish', () => {
+      const msg = `${ip} ${method} ${originalUrl}`;
+      this.logger.log(msg);
+      console.log('msg', msg);
+    });
+
+    next();
+  }
+}

+ 2 - 1
packages/backend/src/main.ts

@@ -5,7 +5,7 @@
  * @Email: zclzone@outlook.com
  * Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
  **********************************/
-
+import * as requestIp from 'request-ip';
 import { NestFactory } from '@nestjs/core';
 import { AppModule } from './app.module';
 import * as session from 'express-session';
@@ -14,6 +14,7 @@ import { Logger } from 'nestjs-pino';
 
 async function bootstrap() {
   const app = await NestFactory.create(AppModule, { bufferLogs: true });
+  app.use(requestIp.mw());
   app.use(
     session({
       secret: 'isme',

+ 1 - 1
packages/backend/src/modules/menu/menu.service.ts

@@ -9,7 +9,7 @@ export class MenuService {
   constructor(
     @InjectRepository(Menu)
     private menuRepo: Repository<Menu>,
-  ) { }
+  ) {}
 
   async create(createMenuDto: CreateMenuDto) {
     const menu = this.menuRepo.create(createMenuDto);

+ 22 - 0
packages/backend/src/modules/operation-log/dto/create-operation-log.dto.ts

@@ -0,0 +1,22 @@
+import { ApiProperty, ApiBody, PartialType } from '@nestjs/swagger';
+import { Exclude } from 'class-transformer';
+import {
+  Allow,
+  IsArray,
+  IsBoolean,
+  IsNotEmpty,
+  IsNumber,
+  IsOptional,
+  IsString,
+  Length,
+} from 'class-validator';
+
+export class CreateOperationLogDto {
+  @ApiProperty()
+  @IsString()
+  @IsNotEmpty({ message: '标题不能为空' })
+  @Length(1, 200, {
+    message: `用户名长度必须大于$constraint1到$constraint2之间,当前传递的值是$value`,
+  })
+  title: string;
+}

+ 4 - 0
packages/backend/src/modules/operation-log/dto/update-operation-log.dto.ts

@@ -0,0 +1,4 @@
+import { PartialType } from '@nestjs/swagger';
+import { CreateOperationLogDto } from './create-operation-log.dto';
+
+export class UpdateOperationLogDto extends PartialType(CreateOperationLogDto) {}

+ 31 - 0
packages/backend/src/modules/operation-log/entities/operation-log.entity.ts

@@ -0,0 +1,31 @@
+import {
+  Column,
+  Entity,
+  PrimaryGeneratedColumn,
+  CreateDateColumn,
+  UpdateDateColumn,
+} from 'typeorm';
+
+@Entity()
+export class OperationLog {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ unique: false, default: '' })
+  title: string;
+
+  @Column({ unique: false, default: '' })
+  userName: string;
+
+  @Column({ unique: false, default: '' })
+  requestUrl: string;
+
+  @Column({ unique: false, default: '' })
+  ip: string;
+
+  @CreateDateColumn()
+  createTime: Date;
+
+  @UpdateDateColumn()
+  updateTime: Date;
+}

+ 34 - 0
packages/backend/src/modules/operation-log/operation-log.controller.ts

@@ -0,0 +1,34 @@
+import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
+import { OperationLogService } from './operation-log.service';
+import { CreateOperationLogDto } from './dto/create-operation-log.dto';
+import { UpdateOperationLogDto } from './dto/update-operation-log.dto';
+
+@Controller('operation-log')
+export class OperationLogController {
+  constructor(private readonly operationLogService: OperationLogService) {}
+
+  @Post()
+  create(@Body() createOperationLogDto: CreateOperationLogDto) {
+    return this.operationLogService.create(createOperationLogDto);
+  }
+
+  @Get()
+  findAll() {
+    return this.operationLogService.findAll();
+  }
+
+  @Get(':id')
+  findOne(@Param('id') id: string) {
+    return this.operationLogService.findOne(+id);
+  }
+
+  @Patch(':id')
+  update(@Param('id') id: string, @Body() updateOperationLogDto: UpdateOperationLogDto) {
+    return this.operationLogService.update(+id, updateOperationLogDto);
+  }
+
+  @Delete(':id')
+  remove(@Param('id') id: string) {
+    return this.operationLogService.remove(+id);
+  }
+}

+ 11 - 0
packages/backend/src/modules/operation-log/operation-log.module.ts

@@ -0,0 +1,11 @@
+import { Module } from '@nestjs/common';
+import { OperationLogService } from './operation-log.service';
+import { OperationLogController } from './operation-log.controller';
+import { TypeOrmModule } from '@nestjs/typeorm';
+import { OperationLog } from './entities/operation-log.entity';
+@Module({
+  imports: [TypeOrmModule.forFeature([OperationLog])],
+  controllers: [OperationLogController],
+  providers: [OperationLogService],
+})
+export class OperationLogModule {}

+ 34 - 0
packages/backend/src/modules/operation-log/operation-log.service.ts

@@ -0,0 +1,34 @@
+import { Injectable } from '@nestjs/common';
+import { CreateOperationLogDto } from './dto/create-operation-log.dto';
+import { UpdateOperationLogDto } from './dto/update-operation-log.dto';
+import { InjectRepository } from '@nestjs/typeorm';
+import { OperationLog } from './entities/operation-log.entity';
+import { Repository } from 'typeorm';
+
+@Injectable()
+export class OperationLogService {
+  constructor(
+    @InjectRepository(OperationLog)
+    private operationLogRepo: Repository<OperationLog>,
+  ) {}
+
+  create(createOperationLogDto: CreateOperationLogDto) {
+    return this.operationLogRepo.save(createOperationLogDto);
+  }
+
+  findAll() {
+    return `This action returns all operationLog`;
+  }
+
+  findOne(id: number) {
+    return `This action returns a #${id} operationLog`;
+  }
+
+  update(id: number, updateOperationLogDto: UpdateOperationLogDto) {
+    return `This action updates a #${id} operationLog`;
+  }
+
+  remove(id: number) {
+    return `This action removes a #${id} operationLog`;
+  }
+}

+ 18 - 0
pnpm-lock.yaml

@@ -79,6 +79,9 @@ importers:
       reflect-metadata:
         specifier: ^0.1.13
         version: 0.1.14
+      request-ip:
+        specifier: ^3.3.0
+        version: 3.3.0
       rxjs:
         specifier: ^7.8.1
         version: 7.8.1
@@ -119,6 +122,9 @@ importers:
       '@types/node':
         specifier: ^20.3.1
         version: 20.17.11
+      '@types/request-ip':
+        specifier: ^0.0.41
+        version: 0.0.41
       '@types/supertest':
         specifier: ^2.0.12
         version: 2.0.16
@@ -2369,6 +2375,9 @@ packages:
   '@types/range-parser@1.2.7':
     resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
 
+  '@types/request-ip@0.0.41':
+    resolution: {integrity: sha512-Qzz0PM2nSZej4lsLzzNfADIORZhhxO7PED0fXpg4FjXiHuJ/lMyUg+YFF5q8x9HPZH3Gl6N+NOM8QZjItNgGKg==}
+
   '@types/semver@7.5.8':
     resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
 
@@ -6196,6 +6205,9 @@ packages:
     resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
     engines: {node: '>=0.10'}
 
+  request-ip@3.3.0:
+    resolution: {integrity: sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==}
+
   require-directory@2.1.1:
     resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
     engines: {node: '>=0.10.0'}
@@ -9487,6 +9499,10 @@ snapshots:
 
   '@types/range-parser@1.2.7': {}
 
+  '@types/request-ip@0.0.41':
+    dependencies:
+      '@types/node': 20.17.11
+
   '@types/semver@7.5.8': {}
 
   '@types/send@0.17.4':
@@ -14310,6 +14326,8 @@ snapshots:
 
   repeat-string@1.6.1: {}
 
+  request-ip@3.3.0: {}
+
   require-directory@2.1.1: {}
 
   require-from-string@2.0.2: {}