前端

React

React 的生命周期?

Ref: https://www.runoob.com/react/react-component-life-cycle.html?utm_source=chatgpt.com

React 生命周期主要为三个阶段

  • 挂载 / Mounting:组件实例被创建并插入 DOM 中时,依次调用
    • constructor():构造函数,在组件挂载之前调用,用于初始化状态或绑定事件处理程序
    • static getDerivedStateFromProps(props, state):在调用 render 方法之前调用,并在初始挂载及后续更新时都会被调用。它应返回一个对象来更新状态,或返回 null 表示状态不需要更新
    • render():唯一必须实现的方法,返回要渲染的 JSX 元素。
    • componentDidMount():在组件挂载后(插入 DOM 树中)立即调用,适合在此进行数据获取或订阅操作。
  • 更新 / Updating:当组件的 props 或 state 发生变化时,会触发更新,依次调用:
    • static getDerivedStateFromProps(props, state):与挂载阶段相同,在更新时也会被调用
    • shouldComponentUpdate(nextProps, nextState):接收新的 props 和 state,返回布尔值,决定组件是否需要重新渲染。默认返回 true
    • render():重新渲染组件
    • getSnapshotBeforeUpdate(prevProps, prevState):在最近一次渲染输出(提交到 DOM 节点)之前调用,允许组件在 DOM 更新前获取快照
    • componentDidUpdate(prevProps, prevState, snapshot):在组件更新后立即调用,可用于操作 DOM 或进行网络请求
  • 卸载 / Unmounting:当组件从 DOM 中移除时,调用以下方法
    • componentWillUnmount():在组件卸载和销毁之前调用,适合在此进行清理操作,如取消订阅、清除计时器等

懒加载,虚拟列表怎么实现的?

  • 占位符设置: 初始时,将需要懒加载的资源(如图片)的 src 属性设置为占位符(如一张小的透明图片),并将真实的资源路径存储在自定义属性(如 data-src)中。
  • 视口监测: 监听浏览器的滚动事件,判断资源元素是否进入视口。
  • 资源加载: 当资源元素进入视口时,将其 src 属性替换为真实的资源路径,触发加载。

虚拟列表仅渲染视口内可见的列表项,以及视口上下的缓冲区,随着滚动动态更新渲染内容。我在写 Next.GenAI 项目的时候就遇到了对话过长需要渲染很多个 Markdown 块的问题。

  • 容器高度计算: 计算整个列表的总高度,以支持滚动。即使只渲染部分列表项,滚动条也能正确反映整个列表的长度。
  • 可视区域计算: 根据滚动位置和视口高度,确定当前需要渲染的列表项的起始和结束索引。
  • 动态渲染: 仅渲染上述计算得到的列表项,并通过绝对定位将其放置在正确的位置。

PureComponent有了解吗?

Ref: https://react.dev/reference/react/PureComponent

PureComponent 是一个用于优化性能的类组件。​它继承自 React.Component,并在内部实现了 shouldComponentUpdate 生命周期方法,对组件的 propsstate 进行浅层比较(shallow comparison)。​当检测到新旧 propsstate 相等时,组件将跳过重新渲染过程,从而提升性能。
即使内部数据发生变化,但外层引用未变,浅层比较可能无法检测到变化,导致组件不会重新渲染。
在函数组件中,可以使用 React.memo 高阶组件来实现类似于 PureComponent 的性能优化。

React的钩子函数

Hook作用适用场景
useState管理组件状态计数器、表单数据、UI 交互状态
useEffect执行副作用(网络请求、订阅、DOM 操作)监听状态变化、API 请求、订阅事件
useContext跨组件共享状态主题切换、用户登录信息
useReducer复杂状态管理(类似 Redux)处理多个状态逻辑
useRef获取 DOM/保存数据不触发渲染操作 input、存储上一次状态
useMemo缓存计算结果,提高性能依赖数据计算,避免重复渲染
useCallback缓存函数,提高性能避免组件不必要的重新渲染
useLayoutEffect同步执行副作用(渲染前)获取 DOM 元素位置
useImperativeHandle自定义暴露给父组件的实例方法ref 传递方法
useTransition优化状态切换的性能处理大列表渲染
useDeferredValue优化输入框防抖处理搜索框输入

react 的状态管理,父子组件之前通信一般用什么

  • 最常用:Props 传递,父子单线数据流;子父通知使用回调
  • useContext 避免 Prop Drilling:多个组件共享状态,避免逐层传递
  • useReducer 适用于复杂的状态管理,多个组件共享状态
  • Redux/Zustand 等包:跨多个组件的复杂管理,全局 Store
  • mitt

Javascript / Typescript

promise用过吗,有什么方法

JavaScript 处理异步操作的重要工具

  • .then() 处理 resolve
  • .catch() 处理 reject
  • .finally() 都执行
  • 同时执行多个 Promise
    • Promise.all() 全部成功才返回
    • Promise.race() 无论成功失败,返回最快的
    • Promise.allSettled() 等待所有执行完,返回每个的状态
    • Promise.any() 返回第一个成功的

CSS

有没有用过flex布局,如何实现垂直居中

是的,我用过 flex 布局,它是现代 CSS 中最强大的布局方式之一。要实现垂直居中,可以使用 align-items: center;
对于多行文本,可能出现高度塌陷,可以尝试 flex-direction: column; 对于未知大小也可以尝试 margin: auto

另:水平居中:justify-content: center

WebRTC

WebRTC 如何实现的 P2P?实际应用下延迟如何?

WebRTC 采用客户端-服务器-客户端模式建立 P2P 连接,主要流程如下

  • 信令交换:SDP/ICE
  • NAT 穿透:STUN/TURN,ICE机制
  • 建立 P2P:UDP/TCP 传输,SRTP加密

我使用 WebRTC 主要是实现的发现对方,正在考虑逐步减轻对 API 服务器的依赖。

并发

Golang

对于传入的请求, Golang 的 I/O 多路复用?

Go 语言的 net 默认使用 netpoll 机制,它基于底层的,无需手动调用 epoll/kqueue,只需要用 net.Listener 监听并处理连接。

你的 Golang 项目如何实现的并发优化?有什么别的思路?

我使用了 Goroutine + WaitGroup,由于并发网络请求才是重点,且数据库不要求实时写入严格,所以没有用高性能数据库或者生产者——消费者模型。

对于发送网络请求,因为 HTTP/2 默认支持 TCP 复用,避免了 HTTP/1.1 的队头阻塞问题。我使用了 http.Transport 的连接池进行了请求。配合 Goroutine 取得了非常不错的效果,实现秒级耗时下的 300 数量级的并发。

未来如果使用人数翻倍,可能考虑会用生产者——消费者,因为我们对数据的写入都是覆盖的,除非是保留历史记录。

  • Goroutine + WaitGroup:并发请求、管理同步
  • Worker Pool:限制并发数,避免资源耗尽
  • 数据库连接池:复用连接,减少建连开销
  • 批量插入:提高数据库写入效率
  • 事务:确保数据一致性
  • 生产者-消费者模型:解耦请求和数据库操作
  • Redis 缓存:降低数据库查询压力
  • 调整 GOMAXPROCS:充分利用 CPU 多核

鉴权

鉴权

网关统一鉴权的核心目标是在请求进入后端服务之前,由网关集中验证请求的合法性(如身份验证和权限检查)。这种方式可以减少每个微服务的重复鉴权逻辑,提高系统的安全性和可维护性。以下是实现网关统一鉴权的常见方案和步骤:

1. 基于 JWT 的鉴权

JWT 鉴权

JWT(JSON Web Token)是实现网关统一鉴权的常用方式。

流程:

  1. 用户登录
    用户通过登录接口认证后,认证服务生成 JWT 并返回给客户端。JWT 中通常包含用户 ID、角色、权限等信息。

    {
      "sub": "user123",
      "roles": ["admin", "editor"],
      "iat": 1516239022,
      "exp": 1516242622
    }
    
  2. 客户端携带 JWT
    客户端在后续请求中将 JWT 放入 HTTP 请求头(通常是 Authorization: Bearer <JWT>)。

  3. 网关验证 JWT
    网关拦截请求,提取 JWT 并进行以下验证:

    • 签名验证:确保 JWT 未被篡改。
    • 过期时间验证:检查 JWT 是否在有效期内。
    • 权限验证:根据 JWT 中的角色或权限检查用户是否有权访问当前资源。
  4. 请求转发
    如果 JWT 验证通过,网关将请求转发给后端服务;否则返回 401 Unauthorized 或 403 Forbidden。

优点:

  • 无状态:网关不需要存储会话信息。
  • 高效:JWT 自包含,验证速度快。
  • 灵活:JWT 可以携带任意业务需要的声明。

2. 基于 OAuth2 的鉴权

OAuth2 鉴权

OAuth2 是一种授权框架,适合分布式系统中的鉴权场景。

流程:

  1. 用户登录
    用户通过 OAuth2 授权服务器获取访问令牌(Access Token)。

  2. 客户端携带 Token
    客户端在请求中将 Token 放入 HTTP 请求头(Authorization: Bearer <Token>)。

  3. 网关验证 Token
    网关向 OAuth2 授权服务器验证 Token 的有效性,并获取用户的权限信息。

  4. 请求转发
    如果 Token 验证通过,网关将请求转发给后端服务;否则返回 401 Unauthorized 或 403 Forbidden。

优点:

  • 标准化:OAuth2 是广泛使用的标准。
  • 安全性:Token 有效期短,支持刷新机制。
  • 适用于第三方授权。

3. 基于 API 密钥的鉴权

鉴权

适用于服务间通信或开放 API 场景。

流程:

  1. 生成 API 密钥
    为每个客户端或服务生成唯一的 API 密钥。

  2. 客户端携带 API 密钥
    客户端在请求中将 API 密钥放入 HTTP 请求头或查询参数。

  3. 网关验证 API 密钥
    网关检查 API 密钥是否有效,并根据密钥关联的权限决定是否允许访问。

  4. 请求转发
    如果 API 密钥验证通过,网关将请求转发给后端服务;否则返回 403 Forbidden。

优点:

  • 简单易用。
  • 适用于服务间通信。

JWT

JWT

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用环境间安全地传输信息。JWT 是一种紧凑的、URL 安全的方式,可以表示需要在双方之间传输的声明(claims)。这些声明通常用于身份验证和信息交换。

JWT 令牌和传统方式有什么区别?

  • 无状态性:JWT是无状态的令牌,不需要在服务器端存储会话信息。相反,JWT令牌中包含了所有必要的信息,如用户身份、权限等。这使得JWT在分布式系统中更加适用,可以方便地进行扩展和跨域访问。
  • 安全性:JWT使用密钥对令牌进行签名,确保令牌的完整性和真实性。只有持有正确密钥的服务器才能对令牌进行验证和解析。这种方式比传统的基于会话和Cookie的验证更加安全,有效防止了CSRF(跨站请求伪造)等攻击。
  • 跨域支持:JWT令牌可以在不同域之间传递,适用于跨域访问的场景。通过在请求的头部或参数中携带JWT令牌,可以实现无需Cookie的跨域身份验证。

JWT 令牌为什么能解决集群部署,什么是集群部署?

在传统的基于会话和Cookie的身份验证方式中,会话信息通常存储在服务器的内存或数据库中。但在集群部署中,不同服务器之间没有共享的会话信息,这会导致用户在不同服务器之间切换时需要重新登录,或者需要引入额外的共享机制(如Redis),增加了复杂性和性能开销。

而JWT令牌通过在令牌中包含所有必要的身份验证和会话信息,使得服务器无需存储会话信息,从而解决了集群部署中的身份验证和会话管理问题。当用户进行登录认证后,服务器将生成一个JWT令牌并返回给客户端。客户端在后续的请求中携带该令牌,服务器可以通过对令牌进行验证和解析来获取用户身份和权限信息,而无需访问共享的会话存储。

由于JWT令牌是自包含的,服务器可以独立地对令牌进行验证,而不需要依赖其他服务器或共享存储。这使得集群中的每个服务器都可以独立处理请求,提高了系统的可伸缩性和容错性。

JWT 结构

xxx.yyy.zzz

  • Header - Base64Url 编码
    • 令牌的类型(即 “JWT”)
    • 所使用的签名算法(如 HMAC SHA256 或 RSA)
  • Payload - Base64Url 编码
    • 三种类型
      • 注册声明(Registered claims):一些预定义的声明,如 iss(签发者)、exp(过期时间)、sub(主题)、aud(受众)等。
        • iss(Issuer):JWT 的签发者。
        • sub(Subject):JWT 的主题(通常是用户 ID)。
        • aud(Audience):JWT 的目标接收者。
        • exp(Expiration Time):JWT 的过期时间(以 Unix 时间戳表示)。
        • nbf(Not Before):JWT 的生效时间(以 Unix 时间戳表示)。
        • iat(Issued At):JWT 的签发时间(以 Unix 时间戳表示)。
        • jti(JWT ID):JWT 的唯一标识符,用于防止重放攻击。
      • 公共声明(Public claims):可以自定义的声明,但应避免与已注册声明冲突。
        • https://example.com/roles:表示用户的角色。
        • https://example.com/email:表示用户的电子邮件。
      • 私有声明(Private claims):用于在同意使用它们的各方之间共享信息。
        • userId:用户的唯一标识符。
        • isAdmin:用户是否为管理员。
  • Signature
    • 签名用于验证消息在传输过程中没有被篡改。签名是使用头部中指定的算法,对编码后的头部、编码后的载荷以及一个密钥进行加密生成的。
    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)

JWT 缺点和泄漏预防?

  • 无法撤销:一旦 JWT 签发,除非它过期,否则无法撤销。可以通过使用短暂的有效期或黑名单机制来缓解这个问题。
  • 安全性依赖于密钥:如果密钥泄露,JWT 的安全性将受到威胁。
  • 泄漏解决
    • 及时失效令牌:当检测到JWT令牌泄露或存在风险时,可以立即将令牌标记为失效状态。服务器在接收到带有失效标记的令牌时,会拒绝对其进行任何操作,从而保护用户的身份和数据安全。
    • 刷新令牌:JWT令牌通常具有一定的有效期,过期后需要重新获取新的令牌。当检测到令牌泄露时,可以主动刷新令牌,即重新生成一个新的令牌,并将旧令牌标记为失效状态。这样,即使泄露的令牌被恶意使用,也会很快失效,减少了被攻击者滥用的风险。
    • 使用黑名单:服务器可以维护一个令牌的黑名单,将泄露的令牌添加到黑名单中。在接收到令牌时,先检查令牌是否在黑名单中,如果在则拒绝操作。这种方法需要服务器维护黑名单的状态,对性能有一定的影响,但可以有效地保护泄露的令牌不被滥用。

Misc

Network

接口怎么做反爬

  • 限制请求频率
  • 身份验证
  • 行为检测:UA/Referer
  • 设备指纹(Fingerprint.js),查看相同指纹多个请求
  • 验证码
  • IP 代理和检测(X-Forwarded-For),封禁
  • 返回动态内容/混淆

Storage

local storage存什么比较合适呢,session storage呢

  • LocalStorage:持久化,不敏感。可以被 JS 直接访问,容易被 XSS 窃取
    • 用户设置
    • 持久化 Token
    • 历史记录
    • 自动填充
    • 新手引导
    • ……
  • SessionStorage:临时,不重要。页面关闭/刷新即消失。不可跨标签页,受 XSS 影响。
    • 临时 Token
    • 中途数据