实习周记——第一周

18 阅读12分钟

初识Node.js,搭建基础环境 学习基本操作

本周是实习的第一周,核心任务是初识Node.js,完成环境搭建并掌握基础入门知识,快速适应实习节奏,为后续深入学习打下基础。

第一天:

我先了解了Node.js的核心概念——它是基于Chrome V8引擎的JavaScript运行环境,能够让JavaScript脱离浏览器运行,主要用于后端开发,具有非阻塞I/O、事件驱动的特点,这也是它高效处理并发请求的关键。也了解了node.js的作用

1. 开发服务器

2. 开发工具类应用(webpack,Vite,Babel)

3. 开发桌面端应用,例如代码编译工具Vscode,设计工具Figma,接口测试工具Postman。这些都要借助electron,底层又是通过node.js来操作的。

随后,我开始搭建开发环境,从Node.js官网下载对应版本的安装包,完成安装后,通过命令行工具输入node -v、npm -v验证安装成功,了解到npm是Node.js自带的包管理工具,用于后续依赖包的安装与管理。

image.png

然后运行了基本语句console.log(‘hello Node.js’);

image.png

这个是在系统终端运行,还可以在编译器里面运行。

后面就对基本语法做了一定的了解(详见附录)。

之后我就新项目做了一个简要的测试  

image.png

image.png

第二天:

之后就查看项目目录结构以及核心文件功能:打开项目文件“用户管理”,重点查看了src目录,逐一了解以下文件的作用

1.main.ts:项目入口文件,负责创建Nest应用实例、启动服务器,核心代码为async function bootstrap() { const app = await NestFactory.create(AppModule); await app.listen(3000); },指定启动端口为3000。

2.app.module.ts:根模块(AppModule),NestJS项目的核心模块,所有其他模块、控制器服务都需在此注册或引入,默认包含@Module()装饰器。

3.app.controller.ts:默认控制器,负责处理请求路由,默认包含一个@Get()接口,返回“Hello World!”。

4.app.service.ts:默认服务,负责封装业务逻辑,默认包含一个getHello()方法,返回“Hello World!”,供控制器调用。

后面又查阅了NestJS的官方文档、理解他的核心架构

1.理解“模块化、分层架构”核心理念:模块化是将项目按功能拆分(如用户模块、商品模块),每个模块包含自己的控制器和服务;分层架构是将“请求处理(控制器)”和“业务逻辑(服务)”分离,控制器只接请求,服务只处理逻辑。

2.区分NestJS与原生Node.js、Express:原生Node.js需手动处理路由、请求响应,代码混乱;Express是轻量级框架,无固定架构规范;NestJS基于Express(或Fastify),提供固定架构、模块化、依赖注入等功能,更适合大型项目开发。

对Module、Controller、Service、Provider核心概念进行了一定初步的了解,如下表:

概念核心作用用户模块具体例子
Module(模块)封装一组相关功能(控制器 + 服务),是 NestJS 组织代码的基本单元Users.Module(可自定义)封装Users.Controller和 Users.Service,注册到根模块
Controller(控制器)接收客户端请求(GET/POST 等)、返回响应,不处理业务逻辑UsersController接收/users 的 GET 请求,调用服务后返回用户数据
Service(服务)封装业务逻辑(数据处理、计算等),供控制器调用,可复用UsersService里写getAllUsers() 方法,封装内存用户数据,给控制器调用
Provider(提供者)可被注入的 “资源”(Service 是最常见的 Provider),Nest 自动管理其生命周期UsersService用@Injectable() 标记为 Provider,控制器通过构造函数 “注入” 后直接使用

之后对着详细项目进行@Module()装饰器参数的系统学习:
imports:引入其他模块,若当前模块需要使用其他模块的服务/控制器,需在此引入(如imports: [OtherModule])。

controllers:注册当前模块的控制器,数组形式,所有控制器必须在此注册才能生效(如controllers: [AppController])。

providers:注册当前模块的Provider(如Service),数组形式,所有Provider必须在此注册才能被注入使用(如providers: [AppService])。

exports:导出当前模块的Provider,供其他模块引入使用,若不导出,其他模块无法使用当前模块的服务(如exports: [AppService])。

又对controller装饰器进行了系统学习:

@Controller():标记类为控制器,可传入路由前缀(如@Controller('users')),后续该控制器下的所有接口都会带上该前缀(如GET /users)。

请求方法装饰器:@Get()(处理GET请求)、@Post()(处理POST请求)、@Put()(处理PUT请求)、@Delete()(处理DELETE请求),装饰在控制器的方法上,指定方法处理的请求类型和路由。

Service:

控制器(Controller)只负责“接收请求、返回响应”,不处理具体业务;服务(Service)负责“处理业务逻辑”,供控制器调用,这样既能提高代码复用性,也便于后续维护和测试(如修改业务逻辑,只需修改Service,无需修改Controller)。

比如“获取用户列表”,控制器负责接收GET /users请求,然后调用Service的getAllUsers()方法(业务逻辑),获取数据后返回给客户端,控制器不参与数据处理。

Service和provider的关系:

Service是Provider的最常见形式,所有Service都必须通过@Injectable()装饰器标记,才能被识别为Provider,进而被注入到控制器或其他Service中。

了解依赖注入:

即控制器需要用服务时,不用手动 new UsersService() 创建实例,只需在构造函数声明依赖,Nest就会自动创建服务实例并“注入”到控制器中。

依赖注入的好处

解耦:控制器不用关心服务怎么创建,只关心怎么用;

复用:多个控制器可共用同一个服务实例;

易测试:测试时可替换服务为 “模拟数据”,不影响控制器逻辑。

第三天:

集中精力编写用户管理模块的CRUD接口,基于之前的控制器和服务,依次实现了获取所有用户、根据ID获取单个用户、创建用户、更新用户、删除用户五个接口,添加了简单的参数验证和异常处理。之后,使用Postman创建了专属集合,逐一测试每个接口,检查接口返回的状态码和数据是否符合预期,调试并解决了接口参数传递、数据处理中的小问题,最终确保所有CRUD接口均能正常运行。

    1.GET /users —— 获取所有用户

image.png

2. POST /users —— 创建新用户

image.png

3. GET /users/:id —— 根据 ID 获取单个用户

image.png

image.png4. PUT /users/:id —— 更新用户信息

image.png 5. DELETE /users/:id —— 删除用户

image.png  

 

 

第四天

对node.js进行了更细致化,更系统化的学习:

1. 当需要持久化保存数据的时候,应该想到文件写入

Readfile异步读取:fs.read file(path[.options],calllbacck)

其中path是文件路径(同级前面是一个点,上级是两个点)options是选项配置,callback是回调函数

例如:

fs.readfile(‘观书有感.txt’,(err,data)=>{
If(err){console.log(‘读取失败’);
Return;
Console.log(data.tostring());
});

或者:

let data =fs.readfilesync(‘.观书有感.txt’);
Console.log(data.tostring());

还有流式读取:

就是一部分一部分地读

Const fs =require(‘fs’);
Const rs =fs.createReadStream(‘../资料/笑看风云.mp4’);
rs.on(‘data’,chunk=>{
Console.log(chunk);
})
Rs.on(‘end,()=>{
Console.log(‘读取完成’);});

后面又学习了,模块系统:

用require导入模块,用module.express导出模块

例如:

add(a,b){
Return a+b;
{
Module.exports={
Add.}//就是做了一个加法的函数

然后如果这个文件放在math.js文件中,那在app.js中就可以这样使用:

Const math= require (./math’);
Console.log (math.add(5,3));

又学习了一下promisereadFile();

const fs = require('fs').promises;
fs.readFile('example.txt', 'utf8')
    .then(data => {
        console.log(data);
    })
    .catch(err => {
        console.error(err);
});

还有和他相关的一些静态方法:promise.all(),等待所有promise完成或者任意一个promise失败;promise.race()返回最先完成或失败的promise;

后面又引用了async/await语法

const fs = require('fs').promises;
async function readFile() {
    try {
        const data = await fs.readFile('example.txt', 'utf8');
        console.log(data);
    } catch (err) {
        console.error(err);
    }
}
readFile();

又使用了express框架,创建了一个简单的espress应用:

const express = require('express');
const app = express();
app.get('/', (req, res) => {
    res.send('Hello, Express!');
});
app.listen(3000, () => {
    console.log('Express 服务器运行在 <http://localhost:3000/>');
});

第五天

  1. 开始是对文件复制进行了一定的了解,

(1)基础读写

const fs =require('fs');
let data = fs.readFileSYnc('../资料/笑看风云.mp4');
fs.writeFileSync('../资料/笑看风云-2.mp4',data);

(2)流式读写


const rs  = fs.createReadStream('../资料/笑看风云.mp4');
const ws  = fs.createWriteStream('../资料/笑看风云-3.mp4');
re.on('data',chunk=>{
ws.write(chunk);
});

相比而言,流式读写更好,所用内存空间更小

(3)rs.pipe(ws)

就是读的东西通过管道进入写中。

  1. 后面又学习了fs文件重命名与移动

(1)文件重命名

fs.rename(Oldpath,newpath,callback)
const fs =require('fs');
fs.rename(./座右铭.txt','./论语.txt',err=>{
if(err){
console.log('操作失败~');
return;
}
console.log(’操作成功');
});

(2)对于文件移动,其实和改文件名差不多,只需要将新路径替换后面那个就行了,简单来说,其实两个的操作本质就是改路径,前者只是处于同一个目录下,名字更改了而已

后面又学习了删除文件:

const fs = require('fs')//导入fs模块
fs.unlink('./guan',err=>{
if(err){
console.log('删除失败');
return;
}
console.log('删除成功');
});

或者用rm方法

fs.rm('./论语.txt',err=>{
if(err){
console.log('删除失败');
# return;
}
console.log('删除成功');
});

后面学了文件夹的相关操作:

1.创建(和文件的创建差别不多,就是方法名改成mkdir 如果是递归创建:就是mkdir(path[.option],callback),里面的.option用rucursive:true填充(表达是递归)

2.读取(和文件的读取也是基本一样的)只是方法名改成readdir

3.删除(同理,和文件删除也几乎一样),但是最好不要用rmdir(后面可能会弃用该方法),最好使用rm

4.查看资源信息也就是方法名改为stat,但是输出方式可以有所变化,console.log(data.isfile)看是否为文件 ,console.log(data.isdirectory)看是否为文件夹。

后面,对要求的系统做了仔细的检查

用户实体定义

export class User {
    id: number;
    name: string;
    email: string;
}

创建用户数据传输对象 (create-user.dto.ts)

import { IsString, IsEmail, MinLength } from 'class-validator';

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

    @IsEmail()
    email: string;
}

业务逻辑层 (users.service.ts)

import { Injectable } from '@nestjs/common';
import { User } from './user.entity';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UsersService {
    private users: User[] = [];

    create(createUserDto: CreateUserDto): User {
        const user = {
            id: this.users.length + 1,
            ...createUserDto,
        };
        this.users.push(user);
        return user;
    }

    findAll(): User[] {
        return this.users;
    }

    findOne(id: number): User {
        return this.users.find(user => user.id === id);
    }

    update(id: number, updateData: Partial<User>): User {
        const user = this.findOne(id);
        if (user) {
            Object.assign(user, updateData);
        }
        return user;
    }

    remove(id: number): boolean {
        const index = this.users.findIndex(user => user.id === id);
        if (index !== -1) {
            this.users.splice(index, 1);
            return true;
        }
        return false;
    }
}

控制器层 (users.controller.ts)

import { Controller, Get, Post, Put, Delete, Body, Param } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './user.entity';

@Controller('users')
export class UsersController {
    constructor(private readonly usersService: UsersService) { }

    @Post()
    create(@Body() createUserDto: CreateUserDto): User {
        return this.usersService.create(createUserDto);
    }

    @Get()
    findAll(): User[] {
        return this.usersService.findAll();
    }

    @Get(':id')
    findOne(@Param('id') id: string): User {
        return this.usersService.findOne(+id);
    }

    @Put(':id')
    update(@Param('id') id: string, @Body() updateData: Partial<User>): User {
        return this.usersService.update(+id, updateData);
    }

    @Delete(':id')
    remove(@Param('id') id: string): { success: boolean } {
        const success = this.usersService.remove(+id);
        return { success };
    }
}

模块定义

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
    controllers: [UsersController],
    providers: [UsersService],
})
export class UsersModule { }

附录:

了解有些api不能在node.js里使用,例如BOM、DOM、AJAX,而console和定时器可以使用,Node.js中的顶级对象为global,也可以用globalThis访问顶级对象

后面学习Buffer:缓冲区,是一个类似Array的对象,用于表示固定长度的字节序列,换句话说就是一段固定长度的内存空间,用于处理二进制数据。

特点:

1.大小固定,无法调整

2.性能较好

3.每个元素占一个字节

(1)创建方法:

1. alloc:

let buf=Buffer.alloc(10);//创建一个10字节的buffer,Buffer是node.js的内置模块
Console.log(buf)

   结果:每一个二进制位都会归零。

2.allocunsafe:

 allocUnsafe:let buf_2=Buffer.allocUnsafe(10);
 Console.log(buf_2);

结果:和上面不一定一样

标识Unsafe的原因:所创建的buffer可能包含旧的内存数据。

选择使用原因:速度快,用的时候稍微注意一下 3. from:将一个字符串或者数组转为buffer(将内容的unicode编码或数组内容转为16进制转为)

代码: let buf=Buffer.alloc(10);

Console.log(buf);
Let buf_2=Buffer.allocUnsafe(10000);
Console.log(buf_2);
Let buf_3=Buffer.from(‘hello’);
Let buf_4=Buffer.from([105,108,111]);
Console.log(buf_4);

(2)buffer与字符串的转换: console.log(buf_4.toString());

(3)buffer元素的读写:比如获取第一个元素的数据,console.log(buf[0]);

(4)如果要二进制:console.log(buf[0].tostring(2));

(5)buffer元素的修改:buf[0]=95;

(6)溢出:

Let buf=Buffer.from(‘hello’);
Buf[0]=361;//此时会舍弃高位数字,保留末尾8位

后面复习了一些计组和操作系统的知识。

fs:fs模块可以实现与硬盘的交互,例如文件的创建、删除、重命名、移动,文件内容的写入、读取,以及文件夹的相关操作。

1. 导入fs模块

const fs = require(‘fs’);

2. 写入文件

fs.writeFile(‘./座右铭.txt’,’三人行,必有我师焉’,err=>{
)};//文件如果不存在,就会自动创建文件,并写入。如果有错误就会传回回调函数(err),写入失败:错误对象,写入成功:null

  fs的同步异步问题:

比如上面的writefile就是异步,一个是至上而下的线程,另一个是输出的i/o线程。回调函数会随着i/o的结束压入任务队列当中,只有等主线线程完成之后才会继续运行。

像fs.writeFileSync()就是同步写入,与writeFile基本类似,只是没有回调函数,结果就是先运行该线程,结束后再运行主线程。

不过,显而易见的是异步效率更高,而同步更加好理解。

fs的追加写入:

const fs=require(‘fs’);//引入fs模块
  fs.appendFile(‘./座右铭.txt’,’则其善者而从之,择其不善者而改之’,err=>{if(err){
  Console.log(‘写入失败--’);//回调函数

});

或者fs.appendFileSync就是同步追加写入。

\r\n是回车换行

Fs.WriteFile也可以实现追加写入:
fs.writeFile(‘./座右铭.txt’,’love love love’,{flag:’a’},err=>{
If(err){
Console.log(‘写入失败’);
Return;
)};
Console.log(‘写入成功’);

追加写入的场景:需要不断更新,持续写入的日志;记录用户访问时间。

  fs的流式写入:

Const fs =require(‘fs’);//导入fs
Const ws =fs.creatWriterStream(‘./观书有感.txt’);//创建写入流对象
Ws.write(‘半亩方塘一鉴开\r\n’);
Ws.write(‘天光云影共徘徊\r\n’);//写入
Ws.close();//关闭(可选)

适合写入频次较高的场景:需要分步输入,但是有需要快速响应。写入较频繁。

文件写入的应用场景:

1. 下载文件2.安装软件3.保护程序日志,如Git4.编译器保存文件5.视频录制(需要持久化保存数据的时候)