当前位置: 首页 > news >正文

node.js基础学习-express框架-路由及中间件(十)

一、前言

Express 是一个简洁、灵活的 Node.js Web 应用框架。它基于 Node.js 的内置 HTTP 模块构建,提供了一系列用于构建 Web 应用程序和 API 的功能,使开发者能够更高效地处理 HTTP 请求和响应,专注于业务逻辑的实现。
其特点包括简单易用、中间件机制丰富、路由系统灵活等。通过使用 Express,可以快速搭建服务器,处理不同类型的请求,如网页渲染、数据接口提供等多种功能。

二、路由

1. 基本的路由格式

一个基本的 Express 路由由 HTTP 方法(如app.getapp.post等)、路径(如'/''/about'等)和一个或多个回调函数组成。例如:

app.get('/', (req, res) => {res.send('Hello World!');
});

这里app.get表示处理 HTTP GET 请求,'/'是请求路径,(req, res) => {... }是回调函数。req代表请求对象,包含了请求的相关信息,如请求头、请求参数等;res代表响应对象,用于发送响应给客户端,如res.send方法用于发送响应内容。

2. 路径匹配

  • 静态路径匹配:如app.get('/about', (req, res) => {... });,只有当请求路径完全匹配/about时,这个路由才会被触发。

  • 动态路径匹配:可以使用参数来创建动态路由。例如,app.get('/users/:id', (req, res) => { const id = req.params.id; res.send(User ${id}); });:id是一个动态参数,当请求路径类似/users/1/users/2等时,req.params.id可以获取到相应的参数值,用于根据不同的用户 ID 等动态信息返回不同的响应。

  • 路径参数的多种匹配方式

  • 可选参数:在路径中使用?来表示可选部分。例如app.get('ab?cd', (req, res) => {... });可以匹配ab或者abcd这样的路径。

  • 通配符匹配:使用*来表示匹配任意字符序列。例如app.get('ab*cd', (req, res) => {... });可以匹配ab后面跟着任意字符再接着cd的路径,如abxyzcd等。

  • 正则表达式匹配:可以直接在路径中使用正则表达式。例如app.get('/ab+cd', (req, res) => {... });匹配ab后面至少有一个字符再接着cd的路径,符合正则表达式ab+cd的模式。

3. 路由方法(HTTP方法)

Express 支持多种 HTTP 方法来定义路由,常见的有:

  • GET 请求:用于从服务器获取数据。例如,获取网页内容、查询用户信息等场景。如app.get('/books', (req, res) => { // 查询书籍信息并返回 });
  • POST 请求:通常用于向服务器提交数据,如提交表单数据、上传文件等。例如,app.post('/login', (req, res) => { // 处理用户登录信息提交 });
  • PUT 请求:用于更新服务器上的数据。例如,更新用户信息、修改文章内容等场景。app.put('/users/:id', (req, res) => { // 根据用户ID更新用户信息 });
  • DELETE 请求:用于删除服务器上的数据。比如删除用户记录、删除文件等。app.delete('/products/:id', (req, res) => { // 根据产品ID删除产品信息 });

三、 Express 中间件

中间件是在请求和响应周期中被调用的函数,它可以访问请求对象(req)、响应对象(res)和应用程序的请求 - 响应循环中的下一个中间件(next)。中间件可以执行各种任务,如日志记录、身份验证、数据预处理等,然后可以选择将请求传递给下一个中间件或者路由处理函数。

1. 中间件的使用方式

单个中间件:在路由处理函数中,可以有一个或多个中间件。例如:

app.get('/home', (req, res, next) => {console.log('This is a middleware');next();
}, (req, res) => {res.send('This is the home page');
});

这里第一个函数是中间件,它先打印一条日志,然后调用next()将控制权传递给下一个函数(这里是路由处理函数),用于发送响应。如果不调用next()函数,下一个中间件将不会被执行。

中间件数组:也可以将多个中间件组合成一个数组来使用。例如:

const func1 = (req, res, next) => {console.log('This is a middleware 1');next();
};
const func2 = (req, res, next) => {console.log('This is a middleware 2');next();
};
const func3 = (req, res, next) => {console.log('This is a middleware 3');next();
};
app.get('/list', [func1, func2, func3], (req, res) => {res.send('This is the list page');
});

当请求/list路径时,会依次执行func1func2func3这三个中间件,最后执行路由处理函数来发送响应。

2. 中间件之间的传值

中间件可以通过reqres对象在中间件之间传递值。例如:

const func4 = (req, res, next) => {req.name = 'John';res.age = 33;next();
};
const func5 = (req, res, next) => {const name = req.name;const age = res.age;res.send(`<h1>Hello ${name}, you are ${age} years old!</h1>`);
};
app.get('/hello', [func4, func5], (req, res) => {
});

func4中间件中,通过req.nameres.age设置了值,然后在func5中间件中可以获取这些值来生成响应。

3. 不同类型的中间件

3.1 应用级中间件

通过app.use()方法来添加应用级中间件,它可以应用于整个应用程序或者特定的路径。如果没有指定路径,中间件会应用于所有的请求路径。例如:

const express = require('express');
const app = express();
app.use((req, res, next) => {console.log('This middleware is called for every request');next();
});
app.get('/hello', (req, res) => {res.send('Hello World');
});
app.listen(3000, () => {console.log('Server is running on port 3000');
});

在这个例子中,定义的中间件会在每个请求到达服务器时被调用,它先打印一条日志,然后通过next()函数将请求传递给下一个中间件或者路由处理函数。

3.2 特定路径的应用级中间件

可以指定中间件应用的路径,这样中间件只会对匹配该路径及其子路径的请求起作用。例如:

app.use('/admin', (req, res, next) => {console.log('This middleware is for /admin and its sub - paths');// 可以在这里进行权限验证等操作next();
});
app.get('/admin/dashboard', (req, res) => {res.send('Admin Dashboard');
});

当请求/admin路径或者以/admin开头的子路径(如/admin/dashboard)时,中间件会被调用。这对于对特定模块或功能进行统一的预处理(如权限验证)非常有用。

3.3 路由中间件

路由中间件和应用级中间件类似,也是通过app.use()在特定路由路径下注册,在路由处理函数之前执行,用于对该路由的请求进行预处理等操作。例如:

app.use('/api/users', (req, res, next) => {console.log('This is a route - level middleware for /api/users');// 可以在这里进行用户相关的预处理,如验证用户是否存在等next();
});
app.get('/api/users', (req, res) => {res.send('List of users');
});

这里的路由中间件会在处理/api/users路由的请求之前被调用,用于对用户相关的请求进行预处理。

3.4 错误处理中间件

错误处理中间件用于捕获和处理在路由处理函数或其他中间件中抛出的错误。它的函数签名与普通中间件略有不同,有四个参数(err, req, res, next),其中err参数用于接收错误对象。错误处理中间件应该放在所有其他中间件和路由定义之后,这样才能捕获它们抛出的错误。例如:

app.use((err, req, res, next) => {console.error(err.stack);res.status(500).send('Something went wrong!');
});
app.get('/error - route', (req, res, next) => {const error = new Error('This is a test error');next(error);
});

在这个例子中,当请求/error - route时,会在路由处理函数中创建一个错误对象并通过next(error)将错误传递给错误处理中间件。错误处理中间件会在控制台打印错误栈信息,然后向客户端返回一个状态码为500(服务器内部错误)的响应,消息为Something went wrong!

3.5 内置中间件

**express.json():**用于解析application/json格式的请求体。在处理 POST 或 PUT 请求,且请求体数据为 JSON 格式时非常有用。例如:

app.use(express.json());
app.post('/data', (req, res) => {const data = req.body;// 处理接收到的JSON数据res.send('Data received');
});

当客户端发送一个application/json格式的 POST 请求到/data路径时,express.json()中间件会自动将请求体中的 JSON 数据解析为 JavaScript 对象,并挂载到req.body上,方便在路由处理函数中使用。

**express.urlencoded({ extended: false }):**用于解析application/x -www-form-urlencoded格式的请求体。通常用于处理 HTML 表单提交的数据。例如:

app.use(express.urlencoded({ extended: false }));
app.post('/form-data', (req, res) => {const formData = req.body;// 处理接收到的表单数据res.send('Form data received');
});

username=jun&password=123456这种格式的数据解析

3.6 第三方中间件

**morgan:**用于日志记录,它可以记录每个请求的详细信息,如请求方法、请求路径、响应状态码等。例如:

const morgan = require('morgan');
app.use(morgan('combined'));

这里morgan('combined')是一种日志格式选项,它会记录详细的请求信息。morgan还有其他日志格式,如'dev'(适合开发环境)、'common'等,开发者可以根据实际需求选择。

**cors:**用于解决跨域资源共享问题。例如:

const cors = require('cors');
app.use(cors({origin: 'http://example.com',methods: ['GET', 'POST'],
}));

这个配置允许来自http://example.com的请求使用GETPOST方法进行跨域访问。可以根据具体的业务场景调整origin(允许的源)、methods(允许的请求方法)等参数来满足跨域需求。

四、 模块化的路由中间件

express.Router是 Express 框架中的一个重要组件,它提供了一种模块化的方式来定义路由。使用Router可以将路由分组并封装到独立的模块中,这有助于组织大型应用程序的路由结构,使代码更加清晰、易于维护和扩展。

1. 创建和使用express.Router实例

创建实例:首先,需要创建一个Router实例。例如:

const express = require('express');
const router = express.Router();

定义路由:在Router实例上可以像在主app对象上一样定义各种 HTTP 方法的路由。例如:

router.get('/', (req, res) => {res.send('This is the root of the sub - router');
});
router.post('/data', (req, res) => {const data = req.body;res.send(`Received data: ${data}`);
});

挂载到主应用:创建并定义好Router的路由后,需要将其挂载到主 Express 应用上。例如:

const app = express();
app.use('/api', router);

这里将router挂载到/api路径下,这意味着router中定义的所有路由实际上是相对于/api路径的。例如,router中的'/'路由实际上对应的是/api/路径,'/data'路由对应的是/api/data路径。

2. 路由模块化的优势

  • 代码结构清晰:通过将相关的路由分组到不同的Router模块中,可以将一个大型应用的路由按照功能模块(如用户管理、产品管理、订单管理等)进行划分。例如,在一个电商应用中,可以有一个userRouter用于处理用户相关的路由(注册、登录、获取用户信息等),一个productRouter用于处理产品相关的路由(产品列表、产品详情、添加产品等),这样的代码结构更易于理解和维护。
  • 复用性增强Router模块可以在不同的应用或者应用的不同部分复用。比如,一个通用的authRouter用于处理身份验证相关的路由(登录、验证 token 等),可以在多个不同的微服务或者应用模块中使用,只要它们遵循相同的接口和认证机制。
  • 团队协作便利:在团队开发中,不同的开发人员可以负责不同的Router模块,这样可以并行开发,减少代码冲突。例如,前端开发人员和后端开发人员可以分别开发与用户界面交互相关的路由和与数据库操作相关的路由,通过定义好的接口(如 API 路由)进行协作。

3. 中间件在express.Router中的使用

Router级别使用中间件:可以在Router实例上使用中间件,这些中间件会应用到该Router所定义的所有路由上。例如:

const loggerMiddleware = (req, res, next) => {console.log(`Received a request for ${req.url}`);next();
};
router.use(loggerMiddleware);

这里定义了一个日志记录中间件loggerMiddleware,并通过router.use()将其应用到router上。这样,router中所有的路由在被访问时,都会先执行这个日志记录中间件。

在特定路由中使用中间件:也可以在Router的特定路由中使用中间件。例如:

const authMiddleware = (req, res, next) => {const token = req.headers.authorization;if (!token) {return res.status(401).send('Unauthorized: No token provided');}// 验证token的其他逻辑next();
};
router.get('/protected - route', authMiddleware, (req, res) => {res.send('This is a protected route');
});

在这个例子中,authMiddleware中间件只应用于/protected - route这个特定的路由。当访问该路由时,会先执行中间件进行身份验证,只有验证通过后才会执行路由处理函数。

4. 嵌套express.Router实例

express.Router实例可以进行嵌套,以创建更复杂的路由层次结构。这在构建具有多层级关系的 API 或者应用程序时非常有用。例如,在一个具有用户组和用户的应用中,可以先有一个groupRouter用于处理用户组相关的路由,在groupRouter内部再嵌套一个userRouter用于处理每个用户组内用户相关的路由。

const groupRouter = express.Router();
const userRouter = express.Router();
// 定义用户组相关的路由
groupRouter.get('/', (req, res) => {res.send('List of groups');
});
groupRouter.post('/', (req, res) => {res.send('Create a new group');
});
// 在用户组路由中嵌套用户路由
userRouter.get('/', (req, res) => {res.send('List of users in the group');
});
userRouter.post('/', (req, res) => {res.send('Add a new user to the group');
});
groupRouter.use('/:groupId/users', userRouter);
const app = express();
app.use('/groups', groupRouter);

在这个例子中,userRouter被嵌套在groupRouter内部。groupRouter处理用户组的基本路由,如获取用户组列表和创建新用户组。userRouter处理用户组内用户的相关路由,如获取用户组内用户列表和添加新用户到用户组。通过groupRouter.use('/:groupId/users', userRouter)userRouter挂载到groupRouter/:groupId/users路径下,这样就创建了一个嵌套的路由结构。当请求/groups/1/users(假设1是用户组 ID)时,会先由groupRouter处理/groups/1部分的路由,然后将请求传递给userRouter处理/users部分的路由。

相关文章:

node.js基础学习-express框架-路由及中间件(十)

一、前言 Express 是一个简洁、灵活的 Node.js Web 应用框架。它基于 Node.js 的内置 HTTP 模块构建&#xff0c;提供了一系列用于构建 Web 应用程序和 API 的功能&#xff0c;使开发者能够更高效地处理 HTTP 请求和响应&#xff0c;专注于业务逻辑的实现。 其特点包括简单易用…...

使用MSYS搭建linux开发环境踩坑笔记

前言&#xff1a; 使用linux系统或虚拟机进行嵌入式linux开发是常规方法&#xff1b; 使用MSYS是用于尝鲜和研究。 由于windows和linux的差异&#xff0c;使用MSYS代替Linux虚拟机会遇到很多坑。 主要原因在于&#xff1a; 1. windows和linux文件系统的差异&#xff1a;win不…...

vue3+ts+vite+ElementPlus上传进度条实时更新(UPLoad和progress)。

需求&#xff1a; 上传文件时&#xff0c;展示进度条实时更新&#xff1a; 下面是代码片段: <!-- 添加媒体弹窗 -- 上传 --><el-dialog v-model"centerDialogVisible" title"媒体信息" width"700" :close-on-click-modal"false&qu…...

AspNet WebAPI 模型绑定问题

继承System.Web.Http.ApiController的Action的Model如果被[Serializable]定义&#xff0c;会导致Model的字段无法绑定。 Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll [Serializable] public class Model {public string id { get; set; } }public MyA…...

Android 图形系统之七:SurfaceFlinger

一. 引言 什么是 SurfaceFlinger&#xff1f;SurfaceFlinger 的核心作用和地位&#xff1f;为什么需要了解 SurfaceFlinger&#xff1f; 二. SurfaceFlinger 的基本概念 Surface 和 SurfaceFlinger 的关系SurfaceFlinger 与图形渲染&#xff08;OpenGL ES 和 Vulkan&#xf…...

14、鸿蒙学习——管理通知角标

针对未读的通知&#xff0c;系统提供了角标设置接口&#xff0c;将未读通知个数显示在桌面图标的右上角角标上。 通知增加时&#xff0c;角标上显示的未读通知个数需要增加。 通知被查看后&#xff0c;角标上显示的未读通知个数需要减少&#xff0c;没有未读通知时&#xff0…...

TongRDS分布式内存数据缓存中间件

命令 优势 支持高达10亿级的数据缓冲&#xff0c;内存优化管理&#xff0c;避免GC性能劣化。 高并发系统设计&#xff0c;可充分利用多CPU资源实现并行处理。 数据采用key-value多索引方式存储&#xff0c;字段类型和长度可配置。 支持多台服务并行运行&#xff0c;服务之间可互…...

[在线实验]-RabbitMQ镜像的下载与部署

镜像下载 docker的rabbitmq镜像资源-CSDN文库 加载镜像 docker load --input rabbitmq.tar 给镜像打标签 这里发现镜像名为none&#xff0c;需要给镜像重命名下 docker tag [镜像id] [新镜像名称]:[新镜像标签] docker tag ebaf409ffbe2 rabbitmq:management 运行镜像…...

Linux 系统文件描述符(File Descriptor)小白级介绍

1. 概述 Linux 遵循"一切皆文件"的理念。在 Linux 系统中&#xff0c;文件描述符是一个索引值&#xff08;非负整数&#xff09;&#xff0c;指向内核为每个进程所维护的该进程打开文件的记录表。 如上所述&#xff0c;每个进程都维护着一张文件描述符表。 文件描述…...

【Verilog】实验二 数据选择器的设计与vivado集成开发环境

目录 一、实验目的 二、实验环境 三、实验任务 四、实验原理 五、实验步骤 top.v mux2_1.v 一、实验目的 1. 掌握数据选择器的工作原理和逻辑功能。 2. 熟悉vivado集成开发环境。 3. 熟悉vivado中进行开发设计的流程。 二、实验环境 1. 装有vivado的计算机。 2. Sw…...

IDL学习笔记(三)OMI数据处理。hdf5文件读取,图像反转,GeoTiff区别,月季年均值计算提取输出,单位转换,运行时间计算

modis Level 2 grid 数据是全球格网化数据。一天的数据全在其中。 modis Level 1 和 2 数据是一景一景的影像。 IDL学习笔记&#xff08;三&#xff09;OMI数据处理 hdf5文件读取单位转换&#xff0c;输出hdf5数据集的图像&#xff0c;并检查图像经纬度是否正确&#xff0c;若错…...

深入浅出:PHP中的数据类型全解析

文章目录 引言理解数据类型标量类型整数 (integer)浮点数 (float)布尔值 (boolean)字符串 (string) 复合类型数组 (array)对象 (object)资源 (resource)NULL 特殊类型Callable强制类型转换 实战案例总结与展望参考资料 引言 在编程的世界里&#xff0c;数据类型是构建任何应用…...

要使用 OpenResty 创建一个接口,返回客户端的 IP 地址,并以 JSON 格式输出

要使用 OpenResty 创建一个接口&#xff0c;返回客户端的 IP 地址&#xff0c;并以 JSON 格式输出 要使用 OpenResty 创建一个接口&#xff0c;返回客户端的 IP 地址&#xff0c;并以 JSON 格式输出方案一解决方案&#xff08;openresty使用cjson&#xff09;说明&#xff1a;使…...

智慧油客:从初识、再识OceanBase,到全栈上线

今天&#xff0c;我们邀请了智慧油客的研发总监黄普友&#xff0c;为我们讲述智慧油客与 OceanBase 初识、熟悉和结缘的故事。 智慧油客自2016年诞生以来&#xff0c;秉持新零售的思维&#xff0c;成功从过去二十年间以“以销售产品为中心”的传统思维模式&#xff0c;转向“以…...

ClickHouse守护进程

背景描述 维护CK过程中&#xff0c;有时候会有CK OOM&#xff0c;并且CK自己没有自动拉起的情况出现&#xff1b;那么这个时候就需要守护进程&#xff0c;最初我不说了Supervisor来做守护进程&#xff0c;但是当我手动kill的时候发现并没有自动拉起。 解决方案 于是乎自己写…...

智能合约

06-智能合约 0 啥是智能合约&#xff1f; 定义 智能合约&#xff0c;又称加密合约&#xff0c;在一定条件下可直接控制数字货币或资产在各方之间转移的一种计算机程序。 角色 区块链网络可视为一个分布式存储服务&#xff0c;因为它存储了所有交易和智能合约的状态 智能合约还…...

SQL面试题——拼多多SQL面试题 求连续段的起始位置和结束位置

拼多多SQL面试题 求连续段的起始位置和结束位置 今天的题目来自拼多多,我们先看一下题目描述 有一张表ids记录了id,id不重复,但是会存在间断,求出连续段的开始位置和结束位置 +---+ | id| +---+ | 1| | 2| | 3| | 5| | 6| | 8| | 10| | 12| | 13| | 14| | 15| +--…...

玩《三角洲行动》遇到游戏运行故障是什么原因?游戏运行故障要怎么解决?预防游戏运行故障问题出现

《三角洲行动》游戏运行故障解析与解决方案&#xff1a;原因、解决与预防 在畅游《三角洲行动》这款充满挑战与激情的游戏时&#xff0c;玩家可能会遭遇各种游戏运行故障&#xff0c;如卡顿、闪退、无法启动等问题。我将结合自己丰富的经验和知识&#xff0c;为大家深入剖析《…...

基于灰色神经网络的订单需求预测

灰色神经网络&#xff08;Grey Neural Network, GNN&#xff09; 是将灰色系统理论与人工神经网络相结合的一种模型&#xff0c;旨在处理不完全信息和小样本问题。灰色神经网络利用灰色系统的预测优势和神经网络的学习能力&#xff0c;能够在信息不完整或数据不充分的情况下实现…...

记录学习《手动学习深度学习》这本书的笔记(三)

这两天看完了第六章&#xff1a;卷积神经网络&#xff0c;巧的是最近上的专业选修课刚讲完卷积神经网络&#xff0c;什么卷积层池化层听得云里雾里的&#xff0c;这一章正好帮我讲解了基础的知识。 第六章&#xff1a;卷积神经网络 6.1 从全连接层到卷积 在之前的学习中&…...

云计算——弹性云计算器(ECS)

弹性云服务器&#xff1a;ECS 概述 云计算重构了ICT系统&#xff0c;云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台&#xff0c;包含如下主要概念。 ECS&#xff08;Elastic Cloud Server&#xff09;&#xff1a;即弹性云服务器&#xff0c;是云计算…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现

摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序&#xff0c;以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务&#xff0c;提供稳定高效的数据处理与业务逻辑支持&#xff1b;利用 uniapp 实现跨平台前…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)

RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发&#xff0c;后来由Pivotal Software Inc.&#xff08;现为VMware子公司&#xff09;接管。RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;用 Erlang 语言编写。广泛应用于各种分布…...

莫兰迪高级灰总结计划简约商务通用PPT模版

莫兰迪高级灰总结计划简约商务通用PPT模版&#xff0c;莫兰迪调色板清新简约工作汇报PPT模版&#xff0c;莫兰迪时尚风极简设计PPT模版&#xff0c;大学生毕业论文答辩PPT模版&#xff0c;莫兰迪配色总结计划简约商务通用PPT模版&#xff0c;莫兰迪商务汇报PPT模版&#xff0c;…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

go 里面的指针

指针 在 Go 中&#xff0c;指针&#xff08;pointer&#xff09;是一个变量的内存地址&#xff0c;就像 C 语言那样&#xff1a; a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10&#xff0c;通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...

网页端 js 读取发票里的二维码信息(图片和PDF格式)

起因 为了实现在报销流程中&#xff0c;发票不能重用的限制&#xff0c;发票上传后&#xff0c;希望能读出发票号&#xff0c;并记录发票号已用&#xff0c;下次不再可用于报销。 基于上面的需求&#xff0c;研究了OCR 的方式和读PDF的方式&#xff0c;实际是可行的&#xff…...