跳到主要内容

Go 官网教程跟学

· 阅读需 6 分钟

go 的官网教程和主流的编程教程网站风格截然不同,应该是没用任何前端框架,纯手糊出来的 ,有一种老式风格的美。

React 官网教程跟学

· 阅读需 6 分钟

我对于前端开发的感受是,实际工程和教程实例代码的差距很大,还有很容易陷在改局部需求,最终变成查文档和写局部 JS 代码的工具人,而失去对项目的总体认识。因为前端一般项目庞大,引入库众多,加上每个库都有自己独特的写法,写多了,就只知其一,不知其二了。

综上,为了建立对 react 项目的整体认知,我就第 N 次过一遍官网的教程,同时记录那些概念和项目结构。

跨域资源共享(CORS)详解

· 阅读需 7 分钟

CORS(Cross-Origin Resource Sharing,跨域资源共享) 是一种浏览器安全机制,允许或限制从不同来源(域名、协议或端口)加载资源的网页进行交互。它解决了浏览器的同源策略(Same-Origin Policy, SOP)限制,允许受控的跨域请求,以保护用户数据的安全。

背景

浏览器的同源策略 规定,网页只能访问与其自身同源(即相同域名、协议和端口)的资源,防止恶意网站获取其他域的数据。这虽然有效提升了安全性,但限制了现代 Web 应用中常见的跨域交互需求,如 API 请求、跨域文件加载等。 CORS 允许在安全的情况下进行跨域请求,提供了一种松散的跨源资源访问机制,使得服务器可以指定哪些外部源可以访问其资源。

CORS 的基本原理

  1. 简单请求(Simple Request) : 当浏览器向不同来源的服务器发出跨域请求时,如果满足一定条件,这个请求被称为简单请求 。例如,GET 或 POST 请求,且请求头为Content-Type: text/plain,这些请求可以直接发送。响应头 : 服务器通过在响应头中设置CORS 相关的头 ,告知浏览器是否允许这个跨域请求。重要的 CORS 响应头
  • Access-Control-Allow-Origin: 指定允许哪些来源访问资源。可以是具体的域名,也可以是*(表示允许任何来源)。
Access-Control-Allow-Origin: https://example.com

请求示例

GET /api/data HTTP/1.1
Origin: https://client.com

响应示例

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://client.com
Content-Type: application/json
  1. 预检请求(Preflight Request) : 对于某些类型的请求,如使用了自定义的请求头,或者使用了非 GET/POST/HEAD 等方法,浏览器会在实际请求之前发送一个预检请求OPTIONS请求),以确保目标服务器允许该请求。预检请求示例
OPTIONS /api/data HTTP/1.1
Origin: https://client.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

预检响应示例

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://client.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 86400
  • Access-Control-Allow-Methods: 指定允许的 HTTP 方法,如GETPOST等。

  • Access-Control-Allow-Headers: 允许使用的自定义请求头。

  • Access-Control-Max-Age: 指定预检请求的结果可以缓存多长时间,减少后续预检请求的次数。

  1. 响应头解释
  • Access-Control-Allow-Origin: 允许的跨域源。

  • Access-Control-Allow-Methods: 允许的 HTTP 请求方法。

  • Access-Control-Allow-Headers: 允许的请求头字段。

  • Access-Control-Allow-Credentials: 是否允许发送凭证(如 Cookies、HTTP 认证等)。值为true时,浏览器允许客户端发送和接收凭证。

Access-Control-Allow-Credentials: true

CORS 的工作流程

  1. 浏览器发起请求 : 当浏览器发起跨域请求时,会在请求头中添加Origin字段,指示请求的来源。

  2. 服务器检查请求 : 服务器接收到请求后,根据自身配置检查Origin,并决定是否允许该请求。如果允许,服务器会在响应头中返回相应的 CORS 头信息。

  3. 浏览器处理响应 : 浏览器根据服务器返回的Access-Control-Allow-Origin等 CORS 头信息,决定是否允许页面访问返回的数据。如果服务器没有正确设置这些头,浏览器将阻止请求并抛出 CORS 错误。

CORS 示例

  1. 简单请求 : 前端 JavaScript 代码:
fetch("https://api.example.com/data", {
method: "GET",
credentials: "include", // 发送凭证,如Cookie
})
.then((response) => response.json())
.then((data) => console.log(data))
.catch((error) => console.error("Error:", error));

假设 api.example.com 设置了 CORS 响应头:

Access-Control-Allow-Origin: https://client.com
Access-Control-Allow-Credentials: true
  1. 预检请求 : 当请求使用了非简单的 HTTP 方法(如PUTDELETE),或自定义请求头(如Authorization),浏览器会首先发送预检请求:
OPTIONS /api/data HTTP/1.1
Origin: https://client.com
Access-Control-Request-Method: PUT

服务器响应:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://client.com
Access-Control-Allow-Methods: PUT, GET, OPTIONS
Access-Control-Allow-Headers: Authorization

如何配置 CORS

  1. Node.js/Express : 使用cors中间件:
const express = require("express");
const cors = require("cors");
const app = express();

app.use(
cors({
origin: "https://client.com", // 允许的来源
credentials: true, // 允许发送Cookies
})
);

app.get("/api/data", (req, res) => {
res.json({ message: "Hello World!" });
});

app.listen(3000);
  1. Nginx : 可以通过 Nginx 配置 CORS 响应头:
server {
location /api/ {
add_header 'Access-Control-Allow-Origin' 'https://client.com';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
}
}

常见问题

  • CORS 错误 :当浏览器禁止跨域请求时,会抛出 CORS 错误。这通常是因为服务器没有正确配置 CORS 响应头,或者浏览器不允许请求中的某些操作(如发送凭证)。

  • 如何调试 :可以通过浏览器的开发者工具查看请求和响应头,检查Access-Control-Allow-*相关头信息是否正确配置。

总结

CORS 是为了解决同源策略下的跨域资源请求问题的浏览器机制。它通过一系列的 HTTP 请求头,让服务器控制哪些来源可以访问其资源,并规定了如何安全地进行跨域请求。在实现跨域请求时,确保服务器配置正确的 CORS 响应头是关键。

WEB服务安全问题概述

· 阅读需 5 分钟

常见漏洞类型

  1. 分布式拒绝服务攻击(DDoS - Distributed Denial of Service) DDoS 攻击通过让大量的设备同时向目标服务器发送请求,耗尽其资源,导致服务器无法处理正常的请求,最终使服务无法使用。
  • 目标 :服务器、网站或网络资源。

  • 方法 :利用僵尸网络(Botnet)来发起大规模流量攻击。

  1. SQL 注入(SQL Injection) SQL 注入是一种攻击方式,攻击者通过输入恶意 SQL 代码,绕过身份验证或操纵数据库查询,访问或修改敏感数据。
  • 目标 :使用 SQL 数据库的网站或应用程序。

  • 后果 :窃取或删除数据库中的敏感信息,或篡改应用行为。

  1. 跨站脚本攻击(XSS - Cross-Site Scripting) XSS 是一种代码注入攻击,攻击者通过向网页注入恶意脚本,当用户浏览受感染的网页时,恶意代码在用户的浏览器中执行。
  • 目标 :Web 应用程序用户。

  • 后果 :窃取用户的会话令牌、用户凭证,或者在用户浏览器上执行恶意操作。

  1. 暴力破解(Brute Force Attack) 暴力破解是一种尝试所有可能的密码组合,直到找到正确密码的攻击方法。
  • 目标 :用户登录系统、SSH、RDP 等需要密码的系统。

  • 防御 :使用强密码、账户锁定策略和双因素认证。

  1. 文件包含攻击(File Inclusion Attack) 这种攻击发生在 Web 应用程序允许用户动态加载文件的情况下。攻击者利用漏洞加载恶意文件,执行不安全操作。
  • 目标 :PHP、ASP 等动态加载文件的 Web 应用程序。

  • 后果 :攻击者可能获得服务器的控制权或敏感数据。

  1. 跨站请求伪造(CSRF - Cross-Site Request Forgery) CSRF 攻击诱骗已登录的用户在未察觉的情况下执行恶意操作。例如,在用户登录某网站时,诱导其点击特制的链接,导致未经授权的操作。
  • 目标 :Web 应用中的合法用户。

  • 后果 :攻击者可以利用用户的身份执行未经授权的操作。

authentication 和 authorization 区别

总结:

特性Authentication(身份验证)Authorization(授权)
目的确认用户身份(你是谁)决定用户能做什么(有什么权限)
过程验证用户凭证(用户名、密码、指纹等)检查用户是否有权限访问特定资源或执行特定操作
顺序首先进行身份验证身份验证之后进行授权
实例用户输入用户名和密码,系统验证其合法性用户登录后,系统决定用户能否查看特定页面或文件
应用范围登录、身份识别、用户认证系统权限管理、资源访问控制、功能限制

实际场景:

  • 身份验证 :你输入用户名和密码登录银行网站。

  • 授权 :你登录成功后,银行网站决定你可以查看账户余额,但不能访问管理员功能。

在实际应用中,身份验证和授权经常结合使用。例如,在 OAuth 2.0 中,用户首先通过身份验证登录(Authentication),然后授权应用程序访问其资源(Authorization)。

NodeJS child_process exec原理

· 阅读需 6 分钟

今天工作中,遇到一个难缠的需求:在 nodejs 中用命令行方式运行一段 etl 脚本。数据库是 SAP 公司的 hana,相对冷门,驱动的接口和其他也有区别。需求中用到了await-exec这个库,其背后是 child-process,即用于创建和控制子进程。这个模块允许你在 Node.js 应用中运行外部命令、脚本或其他 Node.js 进程,并与这些子进程进行通信。通过 child_process,你可以执行 shell 命令、启动其他程序,甚至是在独立的 Node.js 环境中运行代码。

child-process 简介(AI)

child_process 是 Node.js 标准库中的一个模块,用于创建和控制子进程。这个模块允许你在 Node.js 应用中运行外部命令、脚本或其他 Node.js 进程,并与这些子进程进行通信。通过 child_process,你可以执行 shell 命令、启动其他程序,甚至是在独立的 Node.js 环境中运行代码。child_process 提供了几种方法来创建和管理子进程:

  1. exec
  • 用于执行一个命令,并且将整个命令的输出缓存在内存中。

  • 适合用于执行简单的命令,例如调用一个 shell 命令并收集结果。

  • 代码示例:

const { exec } = require("child_process");

exec("ls -lh", (error, stdout, stderr) => {
if (error) {
console.error(`执行出错: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
  1. spawn
  • 用于启动一个新的进程,并且可以通过流(streams)与该进程进行交互。

  • 适合用于处理大输出或者需要与进程进行持续交互的场景。

  • 代码示例:

const { spawn } = require("child_process");

const ls = spawn("ls", ["-lh", "/usr"]);

ls.stdout.on("data", (data) => {
console.log(`stdout: ${data}`);
});

ls.stderr.on("data", (data) => {
console.error(`stderr: ${data}`);
});

ls.on("close", (code) => {
console.log(`子进程退出码: ${code}`);
});
  1. fork
  • 专门用于创建一个新的 Node.js 进程,并且能够很方便地在父子进程之间传递消息。

  • 适合用于多进程处理的场景,例如将任务分配给多个子进程来并行处理。

  • 代码示例:

const { fork } = require("child_process");

const child = fork("child.js");

child.on("message", (message) => {
console.log("来自子进程的消息:", message);
});

child.send("开始处理");
  1. execFile
  • 类似于 exec,但 execFile 直接执行一个可执行文件,而不是通过 shell。

  • exec 更加安全,因为不涉及 shell 命令解析。

  • 代码示例:

const { execFile } = require("child_process");

execFile("node", ["--version"], (error, stdout, stderr) => {
if (error) {
console.error(`执行出错: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
});

主要用途:

  • 自动化任务 :可以用来执行 shell 脚本或其他命令行工具,自动化各种任务。

  • 与系统交互 :能够直接调用系统命令或运行外部程序。

  • 多进程处理 :通过 forkspawn 创建多个进程来并行处理任务,提升性能。child_process 模块在需要与操作系统或其他程序交互时非常有用,同时也可以用来在 Node.js 中实现多进程并发处理。

痛苦的调试过程

在主进程脚本文件中,调用 cp 的代码片段:

const pipelineRet = await exec(`${tapCMD} | \
$TARGET_CLICKHOUSE_CMD -c $TARGET_CLICKHOUSE_HOME/config.json`);

其中 tapCMD 大致内容是node xxx.js。我发现,在 xxx.js 中无论打多少次 log,控制台都不会显示。我大概猜到是因为不在同一个进程的原因。只有在 throw Error 时,控制台才会打印出错误。可是这样一来,throw error 后面的代码就不会运行,给调试带来极大的不方便。后来,我研究了一下 cp 的 api,发现本不必如此麻烦。

const { exec } = require("child_process");

exec("node xxx.js", (error, stdout, stderr) => {
if (error) {
console.error(`执行出错: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});

在 xxx.js 执行完毕后(或者意外退出),就会执行后面的回调函数,error 是 Error 加 stderr,steerr 对应的是 console.error 级别,stdout 就是 console.log。如果 error 存在,就会进入 if 中,return 返回,自然不会执行后面的打印 stdout。await-exec库的源代码与上面逻辑类似。

其实,正确的调试方式是 console.error 来取代 log,这样调试起来方便多了。

NoSQL老大-MongoDB的使用

· 阅读需 2 分钟

数据层级关系

mongodb 数据分为三层,分别是 db,collection,document。collection 相当于 table,document 就是一条条的 record。

工具

mongodb 安装时会自带一个名为 mongosh 的 shell,而这个 shell 本质上也是一个 JavaScript 解释器。所以在里面可以直接执行 js 命令。

mongosh

实际使用中似乎很少用 mongosh 的,不太直观。市面上有一些 mongo 的可视化管理工具,比如官方的 MongoDB Compass,Navicat for MongoDB 等。推荐用免费的 MongoDB Compass。

CRUD

updateOne/updateOne 主要区别

  • 更新数量 :

    • updateOne 只更新第一个匹配的文档。

    • updateMany 会更新所有匹配的文档。

  • 使用场景 :

    • 使用 updateOne 当你只想更新单个文档,通常是对唯一标识(如 _id)进行操作时。

其他的 one 和 many 都类似。

查询操作的灵活性

mongoDB 的 json 式查询带来了很多灵活性,我们可以像编程一样写查询。比如说操作符:

const cursor = db.collection("inventory").find({
status: { $in: ["A", "D"] },
});

aggregation pipeline

聚合管道,不只有聚合操作。实际上是一个数组,每个元素为 stage,并按排列顺序执行。

db.orders.aggregate([
// Stage 1: Filter pizza order documents by pizza size
{
$match: { size: "medium" },
},

// Stage 2: Group remaining documents by pizza name and calculate total quantity
{
$group: { _id: "$name", totalQuantity: { $sum: "$quantity" } },
},
]);

计算机路径分隔符之辩-斜杆和反斜杠

· 阅读需 6 分钟

You:

请从历史和用法等角度,告诉我斜杆和反斜杠作为路径分隔符的区别

ChatGPT:

斜杠(/)和反斜杠(\)作为路径分隔符的区别主要在于它们的历史背景和在不同操作系统中的使用。

历史背景

斜杠(/)

  • 斜杠最早在 Unix 系统中作为路径分隔符使用。Unix 系统是在 20 世纪 60 年代末和 70 年代初开发的,它采用斜杠来分隔目录和文件。这个传统一直延续到了现代,包括 Linux 和 macOS 等 Unix 系操作系统。

  • 互联网的统一资源定位符(URL)规范也采用斜杠来分隔路径。例如,在网址中使用http://www.example.com/path/to/file

反斜杠(\)

  • 反斜杠作为路径分隔符是由 Microsoft 在其操作系统中引入的,最早在 MS-DOS 系统中使用。MS-DOS 是 20 世纪 80 年代初开发的,而在那个年代,计算机技术尚未完全标准化。微软选择了反斜杠,因为在当时的 CP/M 操作系统中,斜杠被用作命令行开关标志(例如dir /w)。

  • Windows 继承了 MS-DOS 的传统,因此在路径中继续使用反斜杠。例如:C:\Program Files\MyApp\file.txt

用法区别

  1. 操作系统依赖
  • 在 Unix 系系统(如 Linux、macOS)和相关的应用程序中,斜杠(/)是默认的路径分隔符。

  • 在 Windows 系统中,反斜杠(\)是默认的路径分隔符。

  1. 可互换性
  • 在现代的 Windows 系统中,斜杠(/)和反斜杠(\)通常可以互换使用,因为许多 Windows 应用程序和 API 都能够识别和处理斜杠。例如,C:/Users/UserName在 Windows 中也能正常工作。

  • 然而,在 Unix 系系统中,反斜杠(\)不是有效的路径分隔符,它通常被用作转义字符。

  1. 编程语言和工具的处理
  • 在许多编程语言中,反斜杠(\)作为转义字符使用。例如,在字符串中使用\n表示换行。因此,在表示路径时需要双反斜杠(\)或使用原始字符串字面量(如 Python 中的 r""),以避免转义问题。

  • 使用斜杠(/)通常可以避免这些问题,尤其是在跨平台开发时。

总结来说,斜杠和反斜杠的使用主要取决于操作系统和历史习惯。在跨平台开发中,推荐使用斜杠(/)以获得更广泛的兼容性和避免潜在的转义字符问题。

双反斜杆

两条反斜杠(\)在路径中也有特定的用法和意义,主要涉及编程语言中的字符串处理和特定环境中的路径表示。

双反斜杠的用法和联系

  1. 转义字符
  • 在许多编程语言中,反斜杠(\)是一个转义字符,用于表示特殊字符或控制字符。例如,\n表示换行,\t表示制表符。在这种情况下,如果需要表示实际的反斜杠字符,就需要使用双反斜杠(\)。

  • 举例来说,如果你在 Python 中想要表示 Windows 路径C:\Program Files\MyApp,可以写成"C:\\Program Files\\MyApp"。第一个反斜杠表示转义,第二个反斜杠才是实际的字符。

  1. 字符串字面量中的路径表示
  • 在一些语言(例如 C#和 Python)中,有一种特殊的字符串字面量形式,允许不对反斜杠进行转义处理。在 Python 中,这称为原始字符串字面量(raw string literal),以r开头,例如:r"C:\Program Files\MyApp". 在这种情况下,双反斜杠并不需要,因为反斜杠不会被视为转义字符。
  1. 网络路径
  • 在 Windows 中,两条反斜杠有时用来表示网络路径或共享资源。例如,\\ServerName\ShareName表示网络服务器ServerName上的共享文件夹ShareName。这种用法在 Unix 系系统中没有对应物。

区别和联系

  • 区别 : 单个反斜杠通常作为路径分隔符使用,但在字符串中它也用作转义字符。为了表示实际的反斜杠字符或避免转义,常常需要用到双反斜杠。

  • 联系 : 双反斜杠的使用通常是为了避免或正确处理转义字符的问题,无论是表示实际的反斜杠字符还是在路径字符串中避免转义。它们在 Windows 路径和网络路径表示中也扮演着重要角色。

总之,两条反斜杠主要用于编程语言中的字符串处理以及在特定环境下表示网络路径或共享资源。