初识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自带的包管理工具,用于后续依赖包的安装与管理。
然后运行了基本语句console.log(‘hello Node.js’);
这个是在系统终端运行,还可以在编译器里面运行。
后面就对基本语法做了一定的了解(详见附录)。
之后我就新项目做了一个简要的测试
第二天:
之后就查看项目目录结构以及核心文件功能:打开项目文件“用户管理”,重点查看了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 —— 获取所有用户
2. POST /users —— 创建新用户
3. GET /users/:id —— 根据 ID 获取单个用户
4. PUT /users/:id —— 更新用户信息
5. DELETE /users/:id —— 删除用户
第四天
对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)基础读写
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)
就是读的东西通过管道进入写中。
- 后面又学习了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.视频录制(需要持久化保存数据的时候)