实习周记——第二周

0 阅读5分钟

这周的学习目标主要是能够将上周的用户管理模块连接数据库并实现持久化操作,可以通过以下内容先进行学习:

1.使用“‘@nestjs/config’管理多环境配置”

2.集成TypeORM(或者Prisma)

3.使用 class-validatorclass-transformer 进行 DTO 校验

4.添加全局异常过滤器 HttpExceptionFilter

接下来完成以下任务:

1.将用户模块改为连接 MySQL 或 PostgreSQL;

2.添加角色表,用户与角色一对多;

3.给接口添加 DTO 校验与异常处理。

接下来,我将按步骤完成本周任务:

一、准备工作

1. 检查当前项目状态

项目结构

用户管理/
├── src/
│   ├── common/           # 公共组件
│   │   ├── filters/      # 异常过滤器
│   │   ├── guards/       # 守卫
│   │   ├── middleware/   # 中间件
│   │   └── pipes/        # 管道
│   ├── config/           # 配置模块
│   ├── roles/            # 角色模块
│   ├── users/            # 用户模块
│   ├── app.controller.ts # 应用控制器
│   ├── app.module.ts     # 应用模块
│   ├── app.service.ts    # 应用服务
│   └── main.ts           # 入口文件
├── .env                  # 环境配置文件
├── package.json          # 项目配置文件
└── tsconfig.json         # TypeScript配置文件

已实现的功能

  • 用户管理:CRUD 操作
  • 配置管理:使用 @nestjs/config
  • 异常处理:全局异常过滤器
  • 数据验证:使用 class-validator

现有依赖

{
  "dependencies": {
    "@nestjs/common": "^8.0.0",
    "@nestjs/config": "^2.0.0",
    "@nestjs/core": "^8.0.0",
    "@nestjs/platform-express": "^8.0.0",
    "@nestjs/typeorm": "^8.0.0",
    "class-transformer": "^0.4.0",
    "class-validator": "^0.13.2",
    "mysql2": "^3.20.0",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.5.0",
    "typeorm": "^0.2.45"
  }
}

2. 安装必要的依赖

所有必要的依赖已经安装完成:

  • @nestjs/config - 管理多环境配置,允许在不同环境(开发、测试)中使用不同的配置
  • @nestjs/typeorm - TypeORM 集成,集成到NestJS依赖注入系统,支持迁移和种子数据
  • mysql2 - 数据库驱动
  • typeorm - ORM 核心库,可以做到跨数据库平台的兼容
  • class-validator - 数据验证

二、配置管理

1. 创建配置文件

.env 文件

  • 配置了数据库连接信息(host、port、username、password、database)
  • 配置了应用端口和环境
# 数据库配置
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=123456
DB_DATABASE=user_management

# 应用配置
APP_PORT=3002
APP_ENV=development

.env.prod 文件

  • 配置了生产环境的数据库连接信息
  • 配置了生产环境的应用端口和环境
# 数据库配置
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=123456
DB_DATABASE=user_management_prod

# 应用配置
APP_PORT=3000
APP_ENV=production

2. 集成配置模块

配置模块 (config.module.ts)

  • 使用 @nestjs/config 模块
  • 配置为全局模块
  • 指定了环境文件路径
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: '.env',
    }),
  ],
})
export class AppConfigModule { }

在主模块中导入配置模块

  • 在 app.module.ts 中导入 AppConfigModule
  • 确保配置在整个应用中可用

三、数据库集成

1. 创建数据库实体

User 实体

  • 包含 id、name、email、roleId 等字段
  • 与 Role 实体建立多对一关系
  • 使用 TypeORM 装饰器进行映射
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { Role } from '../roles/role.entity';

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column()
    email: string;

    @Column()
    roleId: number;

    @ManyToOne(() => Role, role => role.users)
    @JoinColumn({ name: 'roleId' })
    role: Role;
}

Role 实体

  • 包含 id、name、description 等字段
  • 与 User 实体建立一对多关系
  • 使用 TypeORM 装饰器进行映射
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { User } from '../users/user.entity';

@Entity()
export class Role {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column()
    description: string;

    @OneToMany(() => User, user => user.role)
    users: User[];
}

2. 配置数据库连接

在 app.module.ts 中配置 TypeOrmModule

  • 使用 forRootAsync 方法异步配置
  • 从配置服务中获取数据库连接信息
  • 配置自动加载实体和自动同步
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { RolesModule } from './roles/roles.module';
import { AppConfigModule } from './config/config.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigService } from '@nestjs/config';

@Module({
  imports: [
    AppConfigModule,
    TypeOrmModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        type: 'mysql',
        host: configService.get('DB_HOST'),
        port: +configService.get('DB_PORT'),
        username: configService.get('DB_USERNAME'),
        password: configService.get('DB_PASSWORD'),
        database: configService.get('DB_DATABASE'),
        autoLoadEntities: true,
        synchronize: true,
      }),
      inject: [ConfigService],
    }),
    UsersModule,
    RolesModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

3. 修改服务层

UsersService

  • 使用 TypeORM Repository 实现数据库操作
  • 实现 create、findAll、findOne、update、remove 方法
  • 添加角色验证逻辑
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { RolesService } from '../roles/roles.service';

@Injectable()
export class UsersService {
    constructor(
        @InjectRepository(User)
        private usersRepository: Repository<User>,
        private readonly rolesService: RolesService
    ) { }

    async create(createUserDto: CreateUserDto): Promise<User> {
        // 验证角色是否存在
        const role = await this.rolesService.findOne(createUserDto.roleId);
        if (!role) {
            throw new NotFoundException(`Role with id ${createUserDto.roleId} not found`);
        }

        const user = this.usersRepository.create({
            ...createUserDto,
            role,
        });

        return this.usersRepository.save(user);
    }

    async findAll(): Promise<User[]> {
        return this.usersRepository.find({ relations: ['role'] });
    }

    async findOne(id: number): Promise<User> {
        const user = await this.usersRepository.findOne({ 
            where: { id },
            relations: ['role']
        });
        if (!user) {
            throw new NotFoundException(`User with id ${id} not found`);
        }
        return user;
    }

    async update(id: number, updateData: Partial<User>): Promise<User> {
        const user = await this.findOne(id);
        
        // 如果更新了roleId,验证角色是否存在
        if (updateData.roleId) {
            const role = await this.rolesService.findOne(updateData.roleId);
            if (!role) {
                throw new NotFoundException(`Role with id ${updateData.roleId} not found`);
            }
            updateData.role = role;
        }

        Object.assign(user, updateData);
        return this.usersRepository.save(user);
    }

    async remove(id: number): Promise<{ success: boolean }> {
        const result = await this.usersRepository.delete(id);
        return { success: result.affected > 0 };
    }
}

RolesService

  • 使用 TypeORM Repository 实现数据库操作
  • 实现 create、findAll、findOne 方法
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Role } from './role.entity';

@Injectable()
export class RolesService {
    constructor(
        @InjectRepository(Role)
        private rolesRepository: Repository<Role>
    ) { }

    async findAll(): Promise<Role[]> {
        return this.rolesRepository.find();
    }

    async findOne(id: number): Promise<Role> {
        const role = await this.rolesRepository.findOne({ where: { id } });
        if (!role) {
            throw new NotFoundException(`Role with id ${id} not found`);
        }
        return role;
    }

    async create(role: { name: string; description: string }): Promise<Role> {
        const newRole = this.rolesRepository.create(role);
        return this.rolesRepository.save(newRole);
    }
}

四、DTO 校验与异常处理

1. 完善 DTO 定义

CreateUserDto

  • 使用 class-validator 装饰器进行验证
  • 定义了 name、email、roleId 字段的验证规则
import { IsString, IsEmail, MinLength, IsNumber } from 'class-validator';

export class CreateUserDto {
    @IsString()
    @MinLength(2)
    name: string;

    @IsEmail()
    email: string;

    @IsNumber()
    roleId: number;
}

2. 配置全局验证管道

在 main.ts 中配置 ValidationPipe

  • 启用 whitelist 选项,自动过滤非白名单字段
  • 启用 forbidNonWhitelisted 选项,拒绝包含非白名单字段的请求
  • 启用 transform 选项,自动转换请求数据类型
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  const configService = app.get(ConfigService);
  
  // 设置全局前缀
  app.setGlobalPrefix('api');
  
  // 添加全局异常过滤器
  app.useGlobalFilters(new HttpExceptionFilter());
  
  // 添加全局验证管道
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }));
  
  // 从配置中获取端口
  const port = configService.get('APP_PORT') || 3000;
  await app.listen(port);
  console.log(`Application is running on: http://localhost:${port}`);
}
bootstrap();

3. 实现全局异常过滤器

HttpExceptionFilter

  • 捕获并处理 HTTP 异常
  • 统一异常响应格式
  • 包含状态码、时间戳、路径和错误信息
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
        message: exception.message,
      });
  }
}

五、测试与验证

1. 测试数据库连接

  • 启动项目,检查数据库连接是否成功
  • 验证数据库表是否自动创建

2. 测试 API 接口

  • 测试角色管理接口:

GET /api/roles - 获取角色列表

全部角色: 屏幕截图 2026-03-16 100625.png 部分角色:

屏幕截图 2026-03-16 100645.png

POST /api/roles - 创建角色

屏幕截图 2026-03-16 100720.png

  • 测试用户管理接口:

POST /api/users - 创建用户 屏幕截图 2026-03-16 100814.png GET /api/users - 获取用户列表 屏幕截图 2026-03-16 100837.png GET /api/users/:id - 获取单个用户

屏幕截图 2026-03-16 100859.png PUT /api/users/:id - 更新用户

屏幕截图 2026-03-16 100943.png DELETE /api/users/:id - 删除用户 屏幕截图 2026-03-16 101012.png

3. 测试数据库操作

  • 验证用户数据成功存储到数据库

image.png

  • 验证角色数据成功存储到数据库

image.png

  • 验证用户与角色的关联关系正确

六、部署准备

1. 环境配置

  • 配置生产环境的数据库连接
  • 确保环境变量正确设置

2. 构建与部署

  • 运行 npm run build 构建项目
  • 测试生产环境启动

本周额外任务: js前端开发初步学习语言:

1.alert:用于生成弹窗(用法:alert('你好,js'))在script中

image.png

通过这个可以直接在原文件中直接使用,也可以借助外部文件提示弹窗

<body>
    <script src="my.js">
    </script>
</body>

使用外部文件的好处:

1.可以集成代码,如果要复用可以整个搬过去,相当于创建了一个新的公式

2.可读性更高,所以样式很多的话就可以使用外部的js

3.内联js:代码写在标签内部,后面vue可能会学习

js的输出语句:

1.document.write('要出的内容’)//如果是标题还可以添加一个 '<(h1>' 这样显示的就是标题

2.要么就是上面说的对话框

3.或者就是consloe.log,这个主要针对程序员调试使用,在控制台输出

js的输入语句

1.prompt('请输入您的年龄:')

变量声明语句:

let age = 18

所以获取用户输入的语句可以是:let uname = prompt('请输入姓名')

let 数组名 = [数据1,数据2,数据3~]

常量声明:const g = 9.8//常量不允许重新赋值,声明时必须要赋值

后面又学习了数据类型,因为比较简单即同之前的c语言类似,所以略过,后面有一点值得注意:字符串中可以使用"${}"来添加引用其他变量或常量(感觉和超链接很像)来进行文本拼接,但是外面必须要用``(反引)

对于加号可以把数据转换成数字类型,如:consolo.log(+'123')就是把字符串转换成数字型

对于显式转换:用对应的数据类型就可以转(例如:let num =Number(prompt('输入年薪')

所以综合上述简单完成了一个网页报表

<!DOCTYPE html>

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    h2{
        text-align: center;
    }

    table{
        border-collapse: collapse;
        height: 80px;
        margin: 0 auto;
        text-align: center;   
    
    }
    
    th{
        padding: 5px 30px;
    }

    table,
    th,
    td{
        border: 1px solid #000;
    }
</style>
<body>
    <h2>订单确认</h2>
    <script>
        let name = prompt(`请输入商品名称`)
        let price = +prompt(`请输入商品价格`)
        let num = +prompt(`请输入商品数量`)
        let addr = prompt(`请输入收获地址`)
        document.write(`
        <table>
        <tr>
            <th>商品名称</th>
            <th>商品价格</th>
            <th>商品数量</th>
            <th>总价</th>
            <th>收货地址</th>
        </tr>
        <tr>
            <td>${name}</td>
            <td>${price}元</td>
            <td>${num}</td>
            <td>${price*num}</td>
            <td>${addr}</td>
        </tr>
        </table>
        `)
    </script>
    
</body>

</html>

所以结果如图所示:

image.png