54 CHEN

前端工程师后端快速手册

作为前端工程师,拥有一定的后端知识是非常重要的。在日益复杂的Web应用开发中,前端工程师需要更全面地了解后端领域,包括数据库管理、安全性、Web框架的使用、AOP编程思想以及常见语言和框架的特性。本手册将为你提供一个简要但全面的后端知识概览,帮助你更好地理解后端开发的关键要点。

数据库

1. 数据库选择

选择适合项目需求的数据库类型,包括关系型数据库(如MySQL、PostgreSQL)和NoSQL数据库(如MongoDB、Redis)。了解它们的特性、优势和劣势,确保选择符合项目需求的数据库。

名称 特性 优势 劣势 性能情况
MySQL - 关系型数据库- 支持 SQL 查询语言- ACID事务支持 - 成熟的开源数据库,社区庞大- 良好的事务支持- 支持复杂查询优化 - 难以扩展到大规模和高并发情景- 不适用于某些非关系型数据的场景- 读写性能相对较低 适用于传统的关系型数据库应用,如企业级应用
PostgreSQL - 关系型数据库- 支持 SQL 查询语言- ACID事务支持 - 高度的标准兼容性- 支持复杂查询和数据类型- 可扩展性强 - 需要更多的硬件资源支持- 配置和优化较为复杂- 在大量写入和更新时性能相对较低 适用于复杂查询和需要高度标准兼容性的应用
MongoDB - NoSQL数据库,文档存储方式 - 高度的灵活性和扩展性- 支持复杂的数据结构和嵌套文档- 适用于大量读取操作 - 不支持事务(在某些场景下)- 写入操作性能相对较差- 空间占用较大 适用于大规模文档型数据存储,如日志、用户信息等
Redis - NoSQL数据库,键值存储方式 - 读写性能极高- 支持多种数据结构,如字符串、列表、集合等- 内存中存储,快速的读写操作 - 数据持久性相对较差,对断电较为敏感- 不支持复杂查询- 内存占用较高 适用于高速读写操作,如缓存、会话存储等

2. 数据库设计

学会进行合理的数据库设计,包括表的规范化、索引的优化以及关系的建模。良好的数据库设计对于应用性能和扩展性至关重要。

范式 例子 建议
第一范式 (1NF) 每个字段都是不可再分的原子值,无重复列。 - 在每个表中,确保每列包含的是单一的原子值。- 避免使用重复的列,使用关联表的方式处理重复数据。
第二范式 (2NF) 满足1NF,并且非主键列完全依赖于主键。 - 将非主键列与主键相关联,确保非主键列完全依赖于整个主键,而非部分依赖。- 使用适当的联合主键。
第三范式 (3NF) 满足2NF,并且非主键列之间没有传递依赖。 - 避免非主键列之间的传递依赖关系。- 将非主键列直接与主键关联,而非通过其他非主键列关联。
Boyce-Codd范式 满足3NF,并且每一个非主键列都直接依赖于候选键(超码)。 - 将表设计成每个非主键列都直接依赖于超码,不允许存在非主键列对超码的部分依赖。- 通常用于关系数据库。
第四范式 (4NF) 满足BCNF,并且没有多值依赖。 - 将表设计成不存在多值依赖,确保每个非主键列都只依赖于超码的某一部分。- 通常用于关系数据库。
第五范式 (5NF) 满足4NF,并且没有连接依赖。 - 确保表中不存在连接依赖,即通过一个非主键列无法推导其他非主键列。- 通常用于关系数据库。

通常情况下,保持到第三范式是一个不错的基准。这有助于确保数据结构的清晰性和避免冗余。如果项目对性能有较高的要求,可能需要考虑适度的冗余以减少联接操作。在这种情况下,可以考虑到达到 Boyce-Codd 范式 (BCNF) 或更高的范式。

3. 数据库优化

拿MongoDB的索引来说,在性能优化方面发挥着重要的作用,能够加速数据查询、提高数据检索效率。以下是MongoDB索引带来的性能优化方面的一些主要优点:

索引优点 例子 缺点或注意事项
快速数据检索 创建字段上的单字段索引 写入操作可能变慢,索引需要占用存储空间
加速排序操作 创建排序字段的单字段索引 大量索引可能导致内存消耗增加,需要平衡查询和写入性能
支持唯一约束 创建唯一索引 创建索引会占用额外的磁盘空间,维护唯一性可能影响写入性能
提高聚合操作性能 创建聚合操作字段的单字段索引 过多的索引可能导致性能下降,需要谨慎选择索引字段
优化范围查询 创建范围查询字段的单字段索引 需要根据查询模式选择合适的索引类型,不是所有查询都需要索引
加速数据更新和删除 创建更新或删除字段的单字段索引 过多的索引可能导致写入性能下降,不是所有字段都适合创建索引
支持覆盖查询 查询字段在索引中时,执行覆盖查询 需要根据查询需求决定是否创建复合索引
缓存索引数据 利用内存中的索引数据进行查询 需要合理配置内存和索引大小,过大的索引可能导致缓存效果降低

4. 硬件

存储介质的读写速度比较受多种因素影响,包括硬件类型、接口标准、缓存大小等。以下是一些常见存储介质的读写速度比较:

介质 读速度 写速度 注意事项
SSD(固态硬盘) 非常快,高达几千 MB/s 非常快,高达几千 MB/s 寿命受写入次数限制,不宜长时间满负荷写入
SATA硬盘 相对较慢,一般为几百 MB/s 相对较慢,一般为几百 MB/s 机械结构,受机械运动限制,适用于大容量存储
内存 极快,高达几十 GB/s 极快,高达几十 GB/s 临时存储,断电后数据丢失,适用于高性能临时计算

了解了硬件的速度量级,可以帮助我们在设计数据库时更好地平衡读写性能和存储空间。比如,可以将热数据存储在SSD上,将冷数据存储在SATA硬盘上,以达到性能和成本的平衡。

安全

后端是最后一道防线,需要保证数据的安全性。在设计后端应用时,需要考虑到安全性的方方面面,包括数据的加密、用户的认证、权限的控制等。了解一些常见的安全问题和解决方案,可以帮助我们更好地设计安全的后端应用。

1. 数据加密

常见的加密算法在Web上的使用主要涉及数据传输的加密和存储的哈希加密。以下是一些常见的加密算法及其在Web上的应用:

算法或协议 类型 应用
TLS/SSL 加密算法 对称、非对称、哈希 保护网络通信的安全,HTTPS 协议使用 TLS/SSL 加密算法对数据进行加密传输,确保通信的机密性和完整性。
AES (Advanced Encryption Standard) 对称 用于对敏感数据进行加密,例如用户密码在数据库中的存储,或在客户端与服务器之间的加密通信。
RSA 非对称 主要用于数据的数字签名和密钥交换。在Web中,常用于对传输中的对称密钥进行安全地交换,从而保障对称加密的安全性。
SHA-256 哈希 用于生成数据的摘要,常用于密码存储、数字签名等场景。在Web中,常见于存储密码的哈希过程,确保用户密码的安全性。
BCrypt 哈希 专门用于密码哈希加密,通过加盐(salt)和多次迭代的方式提高密码的安全性。在Web中,常用于用户密码的存储。
JWT (JSON Web Token) HMAC SHA-256 用于生成具有签名的令牌,以便在客户端和服务器之间安全地传输信息,例如用户认证信息。在Web开发中,常用于身份验证和信息传递。
OAuth HMAC SHA-256 用于在不同应用之间进行安全的用户身份验证,通过令牌进行授权。在Web开发中,常用于第三方登录和授权。

2. 常见攻击

在设计后端应用时,需要考虑到常见的攻击手段,包括SQL注入、XSS攻击、CSRF攻击等。以下是一些常见的攻击手段及其防范措施:

攻击类型 防范办法
SQL 注入 使用参数化查询或预编译语句来防止恶意注入数据库的 SQL 代码。确保用户输入数据经过正确的验证和转义。
跨站脚本攻击 (XSS) 对用户输入进行合适的转义,避免直接插入到页面中。使用 Content Security Policy (CSP) 防止非法脚本的执行。
跨站请求伪造 (CSRF) 使用随机生成的 CSRF Token,并将其嵌入到表单中。验证请求中的 Token 是否有效,确保只处理来自合法来源的请求。
命令注入 不信任用户输入,对用户输入进行验证和过滤。使用安全的 API 或库来执行系统命令,而不是拼接用户输入。
未经授权的访问 使用强密码,并定期更改密码。实施身份验证和授权机制,限制用户访问权限。使用 HTTPS 加密传输敏感数据。
敏感数据泄露 加密存储在数据库中的敏感数据,使用适当的密钥管理。定期审查和更新访问权限。
文件上传漏洞 对文件上传进行严格的限制,仅允许上传特定类型和大小的文件。确保在保存或提供用户上传的文件之前进行充分验证和处理。
拒绝服务攻击 (DDoS) 使用防火墙、反向代理和负载均衡器来分散和过滤流量。定期进行容量规划和性能优化。
中间人攻击 使用 HTTPS 进行数据传输,确保数据在传输过程中加密。实施合适的身份验证和授权机制。
信息泄露与错误处理 不要将详细的错误信息直接返回给客户端。在生产环境中,将错误信息记录到日志中,并给予用户友好的错误提示。

Web框架

1. 选择合适的后端框架

了解主流的后端框架,如Express(Node.js)、Django(Python)、Spring Boot(Java),并选择适合项目需求的框架。

名称 语言 特性
Express.js JavaScript 简洁、灵活的Node.js Web 应用框架,适用于构建 RESTful API 和单页面应用。
Django Python Python 高级 Web 框架,强调快速开发和 DRY(Don’t Repeat Yourself)原则。
Flask Python 轻量级的 Python Web 框架,简单易用,适用于小型和中型应用。
Spring Boot Java 面向 Java 开发者的快速构建生产级别的 Spring 应用的框架,集成了各种 Spring 生态组件。
Ruby on Rails Ruby Ruby 的全栈 Web 框架,注重开发者友好性和约定大于配置的理念,适用于快速开发 Web 应用。
Laravel PHP PHP Web 框架,提供了优雅的语法、模块化、ORM 和其他工具,适用于构建现代 PHP 应用。
ASP.NET Core C# 跨平台的 .NET 框架,用于构建高性能、现代化、云原生的应用,支持 Web、云、移动和 IoT。
Nest.js TypeScript 基于 TypeScript 的渐进式 Node.js 框架,结合了 OOP、FP 和 FRP 的元素,用于构建可伸缩的服务器端应用。
FastAPI Python 基于 Python 的现代、快速(通过使用 Starlette 和 Pydantic)的 Web 框架,用于构建高性能的 API。

2. Restful API

Restful API是一种设计风格,用于构建可伸缩的Web服务。以下是一些常见的Restful API设计原则:

原则 例子 说明
资源标识符(URI) /users/{id} 每个资源都有唯一的标识符,通过 URI 进行访问和操作。
统一接口(Uniform Interface) HTTP 方法(GET、POST、PUT、DELETE) 使用一致的接口,包括 URI、HTTP 方法、表示形式,以简化系统架构和提高可见性。
状态无关(Stateless) 每个请求都包含足够的信息,服务器不存储客户端的状态。
资源的自描述性(Resource Representation) JSON、XML 资源的表示形式应该包含足够的信息,使得客户端能够理解如何处理资源。
超媒体驱动(HATEOAS) 嵌入链接在资源表示中 资源的表示应该包含相关操作的链接,以引导客户端进行进一步的状态转移。
可缓存性(Cacheability) Cache-Control 头 提供可缓存性,以提高性能和减轻服务器负载。

一个简单的办法判断api的设计是否Restful,可以简单地看一下url中是否存在动词,如果存在,那么就不是Restful的。

3. ORM

ORM(Object Relational Mapping)是一种编程技术,用于将对象模型和关系型数据库之间进行转换,其作用是在关系型数据库和面向对象编程语言之间建立映射关系,使得开发者可以使用面向对象的方式操作数据库,而不必直接处理 SQL 查询和数据库表结构。以下是一些常见的ORM框架:

名称 语言 特性
Django ORM Python 1. 集成于 Django 框架中,用于与数据库交互。2. 提供模型类,用于定义数据库表结构。3. 支持多种数据库后端。
Hibernate Java 1. 针对 Java 编程语言的 ORM 框架,广泛用于 Java EE 项目。2. 支持关系数据库的映射。3. 提供对象和数据库的映射。
Entity Framework C# 1. Microsoft 提供的 .NET 平台上的 ORM 框架。2. 可与各种数据库进行集成。3. 提供 LINQ 查询语言支持。
SQLAlchemy Python 1. Python 编程语言中的 ORM 框架,支持多种数据库后端。2. 提供高度灵活的查询语言。3. 支持事务管理。
Sequelize JavaScript 1. 面向 Node.js 环境的 JavaScript ORM 框架。2. 支持多种数据库,如 MySQL、PostgreSQL、SQLite。3. 使用 Promise 进行异步操作。
Beanie Python 1. 面向 MongoDB 的 Python 异步 ORM 框架。2. 基于 Pydantic 模型定义数据库文档结构。3. 支持异步操作和 MongoDB 特性。
Prisma TypeScript 1. 面向 Node.js 和 TypeScript 的数据库访问工具。2. 自动生成类型安全的查询 API。3. 支持多种数据库后端,如 MySQL、PostgreSQL。

AOP

以上介绍的主要是CRUD的操作,但是在实际的开发中,我们还需要考虑到日志、缓存、事务等方面的问题。这些问题都可以通过AOP来解决。

AOP 是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)与主要业务逻辑进行分离,提高代码的模块性和可维护性。横切关注点指的是在应用程序中散布的与核心关注点(如业务逻辑)无关的功能,例如日志记录、事务管理、安全性等。

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from typing import List

app = FastAPI()

# Sample JWT token validation function
async def get_current_user(token: str = Depends(OAuth2PasswordBearer(tokenUrl="token"))):
    credentials_exception = HTTPException(
        status_code=401,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, "secret_key", algorithms=["HS256"])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    return username

# AOP-style logging aspect
def log_request(endpoint):
    async def wrapper(*args, **kwargs):
        print(f"Received request to {endpoint.__name__}")
        response = await endpoint(*args, **kwargs)
        print(f"Request to {endpoint.__name__} completed")
        return response
    return wrapper

# Applying AOP aspect to an endpoint
@app.get("/items/", response_model=List[str])
@log_request
async def read_items(current_user: str = Depends(get_current_user)):
    return [{"item": "Item 1"}, {"item": "Item 2"}]

在上面的示例中,log_request 函数充当一个 AOP 切面,负责记录请求的开始和结束。这个切面被应用到 /items/ 路由上,通过 @log_request 装饰器实现。这样,我们可以在不修改核心业务逻辑的情况下,添加日志记录等横切关注点。

同步异步和多线程线程池

同步与异步

特征 同步 异步
执行方式 顺序执行,一个操作完成后才执行下一个操作 不按顺序执行,通过回调函数、事件驱动等处理
阻塞与非阻塞 阻塞,操作执行时会阻塞程序,程序等待操作完成 非阻塞,一个操作的开始不会等待上一个操作的完成
效率与性能 可能会导致等待时间过长,影响效率和性能 可以提高效率和性能,因为可以并发执行多个操作
例子 传统的阻塞 I/O 操作,如读取文件 异步 I/O 操作、回调函数、事件驱动,如在网页上进行异步加载资源
编程风格 编码相对简单,但可能导致程序无法执行其他任务 编码可能更复杂,需要处理回调函数、事件监听等,但更灵活地处理多个任务

Javascript异步编程举例

回调函数:

function fetchData(callback) {
  // 模拟异步操作
  setTimeout(() => {
    const data = '异步数据';
    callback(data);
  }, 1000);
}

fetchData((result) => {
  console.log(result); // 在回调函数中处理获取的数据
});

Promise:

function fetchData() {
  return new Promise((resolve, reject) => {
    // 模拟异步操作
    setTimeout(() => {
      const data = '异步数据';
      resolve(data);
      // 或者 reject(new Error('异步操作失败'));
    }, 1000);
  });
}

fetchData()
  .then((result) => {
    console.log(result); // 在Promise的then方法中处理获取的数据
  })
  .catch((error) => {
    console.error(error); // 处理Promise中的错误
  });

async/await:

async function fetchData() {
  return new Promise((resolve) => {
    // 模拟异步操作
    setTimeout(() => {
      const data = '异步数据';
      resolve(data);
    }, 1000);
  });
}

async function getData() {
  try {
    const result = await fetchData();
    console.log(result); // 在async/await中处理获取的数据
  } catch (error) {
    console.error(error); // 处理异步操作的错误
  }
}

getData();

多线程与线程池

线程:

线程是操作系统能够进行运算调度的最小单位,它由线程 ID、程序计数器、寄存器集合和堆栈组成。在多线程环境中,多个线程共享相同的资源,如代码段、数据段等,但每个线程有自己的寄存器和堆栈,用于存储线程私有的数据。

线程池:

线程池是一种管理和复用线程的机制,用于提高线程的利用率和减少线程创建和销毁的开销。线程池中包含一定数量的线程,它们等待分配任务并执行。当有任务到达时,线程池分配一个线程来执行任务,执行完毕后线程不被销毁,而是重新放入线程池,等待下一个任务。这样可以避免频繁创建和销毁线程的开销,提高系统的性能和响应速度。

语言 支持情况
Java 内置支持线程和线程池,通过 java.lang.Threadjava.util.concurrent 包实现。
Python 使用 threadingconcurrent.futures 模块来支持线程,但由于 GIL 的存在,多线程效果受限。
C++ 支持原生线程库,如 <thread><future>,也可使用第三方库,如 Boost 等。
JavaScript 支持异步编程,通过事件循环和回调函数实现,Web Workers 支持在独立的线程中执行脚本。
C# 内置支持线程和线程池,通过 System.Threading 命名空间和 Task 类实现。
TypeScript 类似于 JavaScript,支持异步编程,但不直接操作线程,依赖于事件循环和回调机制。

Docker原理

Docker 利用了 Linux 内核的一些特性,如命名空间(Namespaces)和控制组(Cgroups)。命名空间隔离了进程、网络、文件系统等,而控制组用于限制和监控资源的使用,如 CPU、内存等。Docker 使用联合文件系统(UnionFS)来实现容器镜像的分层。它是一种轻量级、可移植的容器化平台,旨在简化应用程序的部署、管理和扩展。

Docker 的核心概念包括镜像(Image)、容器(Container)、仓库(Repository)和服务(Service)。

镜像: Docker 镜像是容器的基础,它包含了运行应用程序所需的所有文件、库和依赖项。镜像是不可变的,确保在不同环境中具有相同的运行时环境。

容器: 容器是 Docker 镜像的运行实例。每个容器都是相互隔离的,拥有自己的文件系统、进程空间和网络接口。容器提供了一种轻量级、可移植的运行环境。

Dockerfile: Dockerfile 是用于定义 Docker 镜像构建过程的文本文件。通过 Dockerfile,开发人员可以指定应用程序的环境和依赖项,以及如何运行应用程序。

网络和端口映射: Docker 允许容器与主机和其他容器通信。每个容器都有自己的网络命名空间,Docker 提供了网络驱动程序,如 bridge、host 和 overlay,以便配置容器之间的通信。Docker 还支持端口映射,将容器内部的端口映射到主机上的端口,以便外部访问。

容器编排: Docker 提供了容器编排工具,如 Docker Compose 和 Kubernetes,用于管理和协调多个容器的部署、伸缩和更新。其中,Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。通过一个单独的 docker-compose.yml 文件,用户可以配置应用程序的服务、网络、卷等,并使用一条命令启动整个应用。一个简单的 docker-compose.yml 文件如下所示:

version: '3'
services:
  web:
    image: nginx
    ports:
      - "8080:80"
  db:
    image: postgres

Docker 仓库是用于存储和管理 Docker 镜像的中央仓库。它允许用户上传和下载 Docker 镜像,方便在不同的环境中分享和部署应用。Docker 官方提供了公共 Docker Hub 仓库,用户可以在其中找到大量的官方和社区维护的镜像。此外,用户也可以搭建自己的私有 Docker 仓库,以满足特定需求或提高安全性。以下是一些常用的仓库。

名称 说明 地址
Docker Hub Docker 官方提供的公共仓库,包含官方和社区维护的镜像。 Docker Hub
Amazon ECR 亚马逊提供的云服务,用于存储、管理和部署 Docker 镜像。 Amazon ECR
Google Container Registry Google Cloud 提供的 Docker 镜像仓库服务,与 Google Cloud Platform 集成。 GCR
Azure Container Registry 微软 Azure 提供的 Docker 镜像仓库服务,与 Azure 云平台集成。 ACR

Docker 服务是在 Docker 中用于定义和运行分布式应用程序的抽象层。服务可以包括多个容器实例,这些实例可以在多个节点上运行,从而实现负载均衡和高可用性。Docker 服务通常与 Docker Compose 结合使用,通过 docker-compose.yml 文件定义服务的配置。

References

https://fastapi.tiangolo.com/

https://spring.io/projects/spring-boot/

https://www.prisma.io/

https://www.sqlalchemy.org/

https://beanie-odm.dev/

https://hub.docker.com/

#Database #Web #Security