大厂一面,刨析题型,把握趋势🔭💯

首先就是面试的常规步骤

  • 自我介绍
  • 提问自己在项目中有哪些优势,项目中的难点是如何解决的?
  • 完整介绍一下项目

开胃小菜第一题:懒加载

  • 组件懒加载 :使用import()动态导入组件。
  • 图片懒加载 :可以通过第三⽅库(如vue lazyload)或原⽣API(如IntersectionObserver)来实现。

第二题:大文件上传以及应用场景的优化

  • 分片上传:将大文件分割成许多小文件块,分批次上传,即使是上传的过程中出现中断,也可以从中断的地方重新开始上传,不用从头开始。

工作原理:

    1. 客户端分割文件:,客户端将文件分割成若干小块。
    1. 逐个分片上传:客户端将每个分片逐个上传到服务器,每次上传一个分片。上传过程中,客户端会与服务器进行通信,更新上传进度。
    1. 服务器接收并保存分片:服务器接收到每个分片后,会存储它们并等待所有分片上传完成。一些服务器会先存储分片,然后返回一个分片 ID 供客户端跟踪。
    1. 合并文件分片:服务器将所有分片合并成完整的的文件
    1. 返回上传结果:服务器将合并后的完整文件保存,并返回一个成功响应,告诉客户端上传完成。
  • 断点续存:顾名思义就是 断点+续存
    • 断点: 其分两种情况->第一种就是由于网络问题导致上传发生中断,还有一种就是我们手动点击暂停上传。

手动实现断点原理:

第一种:基于上传状态管理

通过标志变量 isPaused 控制上传的暂停与恢复,当客户端上传一个分片时,服务器会将该分片的状态记录到数据库中。所有我们可以通过手动修改状态来实现对文件上传的控制。

第二种:基于 XMLHttpRequest.abort() 的手动断点续传

在文件上传过程中,我们将每个分片的 XMLHttpRequest 对象保存下来。当用户点击暂停时,可以调用 abort() 来取消当前上传的请求。这样,上传的请求就会被中止。

  • 续存:客户端会将已上传的片段信息保存到本地(如 localStorage)或者服务器端。在上传过程中,客户端定期向服务器查询已上传的切片,或者服务器在每次上传请求中返回哪些切片已经上传。这可以通过接口实现,客户端根据服务器返回的结果决定从哪一个切片开始上传,跳过已经上传的部分。

第三题:继续提问断点续存---怎么确定上传完了?

首先当所有的切片上传完毕之后,客户端会向服务器端发送合并请求,然后服务器会检查所有切片是否上传完毕,通常会使用文件的哈希值来进行验证,在文件上传前计算文件的哈希值,并将哈希值随文件一起传输,服务器合并文件后,再次计算文件的哈希值并与客户端传输的哈希值进行对比,如果一致则表明文件上传成功。简单来说就是客户端和服务器端都需要记录每个切片的上传状态,确保没有遗漏。

第四题:如何客户端上传到一半,重新刷新页面,要不要重新上传

不需要!上面讲到的断点续存会将分片的上传状态储存到本地和服务器端,在客户刷新页面后可以根据已经上传的切片信息跳过已上传的切片,从剩余的切片开始继续上传。

第五个提问:文件分片的id储存在哪?

  • 客户端:客户端通常会将文件切片的状态(包括分片的 ID、上传进度等)保存在本地存储中,这样可以在页面刷新后恢复上传进度。常用的本地存储方式有 localStoragesessionStorageIndexedDB
  • 服务器端:服务器端通常会根据文件的名称和切片的索引生成一个唯一的 ID 来标识每个文件切片,并保存这些信息。服务器端会记录每个文件切片的状态,包括切片的 ID、上传进度等信息,以便在客户端发生刷新或断点续传时进行恢复。

阿里云OCR是做什么的,有什么用?

OCR:Optical Character Recognition,光学字符识别。 阿里云 OCR是阿里云提供的一种基于深度学习和人工智能技术的服务,能够将图像中的文字信息提取出来并转换为机器可读的文本。

其主要功能包括:

  • 通用文字识别:能够识别图片中的通用文本,无论是印刷文本还是手写文本(部分支持),可以从图像中提取出文字内容。
  • 身份证识别:专门针对身份证等证件进行识别,提取出身份证上的信息,如姓名、出生日期、身份证号码等
  • 银行卡识别:识别银行卡正面的卡号、有效期、持卡人姓名等信息。
  • 车牌识别:识别汽车的车牌号码,通常结合视频流实时识别。
  • 多语言文字识别:支持多语言的文字识别,不仅限于中文,还支持英文、日文、韩文等语言。
  • 等等等......

追问:为什么要用阿里云OCR?

  1. 阿里云OCR拥有高精度的文字识别能力,利用深度学习,人工智能和大数据技术,提供了高准确度的文字识别能力。
  2. 提供多种专业场景支持,阿里云OCR提供了多种OCR模块可以满足不同行业和需求。
  3. 可拓展和易用性,阿里云OCR可以灵活拓展根据实际的需求和流量进行动态扩容。且用户无需深入理解复杂的人工智能技术,只需调用阿里云 OCR API就能快速利用 OCR 技术进行文字识别。
  4. 安全性和稳定性 ,阿里云 OCR 服务保证了数据的安全性和隐私保护。阿里云采用了严格的 数据加密身份认证访问控制 等安全措施,确保用户数据不会外泄。
  5. 节约成本,按需付费,阿里云 OCR 基于成熟的 AI 模型和算法,企业可以直接调用服务,而不需要进行复杂的模型训练。阿里云 OCR 支持按使用量计费,企业可以根据实际需求灵活支付,避免了高额的初期投资。

jwt解决了什么问题?为什么要用JWT

JWT :是一种用于在网络应用环境中安全地传输声明的开放标准,主要用途是实现 身份验证信息交换 。 通俗来讲:JWT就像一张"加密的电子门票",用来安全地在不同系统之间传递用户信息。就比如我们在去游乐园的时候

  1. 买票时

    • 你在窗口出示身份证(登录 ),工作人员验证后,给你一张带印章的门票(JWT)。
    • 门票上印有你的姓名、有效期、允许玩的设施(用户信息+权限)。
  2. 玩项目时

    • 每个设施的工作人员只需检查门票印章 (验证JWT签名),不用再联系售票处查你的身份(无需查数据库)。
    • 如果门票过期或被涂改(无效JWT),直接拒绝进入。

所以JWT解决了这些问题: 将所有的身份信息(如用户 ID、权限、过期时间等)编码 到一个独立的 Token 中,发送给客户端。客户端在后续的请求中,直接携带这个 JWT 作为 请求头的一部分,服务器根据 JWT 验证用户身份。

JWT的组成

一个JWT令牌通常是这样的字符串:

复制代码
xxxxx.yyyyy.zzzzz
  • header(头):说明令牌类型和签名算法
json 复制代码
{ "alg": "HS256", "typ": "JWT" }
  • Payload(数据):存放实际传递的信息(比如用户ID、过期时间)
json 复制代码
{ "sub": "123", "name": "张三", "exp": 1735689600 }
  • Signature(签名) :前两部分+密钥生成的防伪标记,防止数据被篡改。

为什么要使用JWT

  • 无状态 & 减轻服务器压力 在传统解决认证时使用session:用户登录后,服务器要存Session,每次请求都得查一次数据库/缓存,验证用户是否有效。这就有一个问题了,当用户量大了,服务器存Session压力大,扩展麻烦。 而JWT的方式:服务器签发JWT后,不用存任何东西 ,客户端自己保管令牌。后续请求只需验证JWT签名和有效期,不用查数据库

  • 解决了传统 cookie + session 的跨域问题

    因为浏览器的同源策略 Cookie 不能跨域 而Session需要在 Cookie 中存储 SessionID 所以传统的这种方式不能解决跨域问题,而JWT是通过HTTP请求头来传递的不存在跨域问题。

  • 更灵活和可拓展性

    用户的身份信息都存储在JWT,这使得JWT 在各种场景下都可以灵活应用。

  • 简洁,安全

    JWT 是轻量级的,便于操作和解析,同时通过签名确保信息的完整性和安全性

追问 JWT的双Token

首先为什么需要双token?

  • 当Token被盗取后,攻击者可以在短期内一直冒充用户
  • 用户需要频繁重新登录
  • 服务器无法强制废除未过期的Token

双Token机制

使用 访问 Token(Access Token)刷新 Token(Refresh Token) 这两种 Token 来实现双 Token。

  • 访问 Token : 用于授权用户访问受保护的资源,一般有效期比较短,以降低令牌泄露后的风险,通常会存储在客户端的内存中或 HTTP 请求头
  • 刷新Token :用于获取新的访问 Token,通常在访问 Token 过期后,使用刷新 Token 来获取新的访问 Token,而不需要重新登录,有效期通常为几天或几个月,存储在 HTTP-only cookie 中,确保它不容易被 JavaScript 脚本访问,增加安全性。

双 Token的工作流

继续追问 在代码逻辑上怎么去使用 Access Token和 Refresh Token这两个字段?

  • 前端方面
    *
    1. 登录时保存 Token
      1. 请求时携带 Access Token
      1. Token 过期时自动刷新
      1. 退出时清除 Token
js 复制代码
// src/utils/auth.js
import axios from 'axios';

// 1. 保存 Token
export const saveTokens = (accessToken, refreshToken) => {
  localStorage.setItem('accessToken', accessToken);
  // Refresh Token 由后端通过 HTTP-Only Cookie 存储,前端不处理
};

// 2. 获取当前 Access Token
export const getAccessToken = () => {
  return localStorage.getItem('accessToken');
};

// 3. 清除 Token
export const clearTokens = () => {
  localStorage.removeItem('accessToken');
  // 调用后端接口清除 Refresh Token(见后端部分)
};

// 4. 配置 Axios 实例
const api = axios.create({
  baseURL: 'https://api.yourdomain.com',
});

// 5. 请求拦截器(自动添加 Access Token)
api.interceptors.request.use((config) => {
  const token = getAccessToken();
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 6. 响应拦截器(自动刷新 Token)
api.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    
    // 如果是 401 错误且未重试过
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
      
      try {
        // 调用刷新接口获取新 Access Token
        const res = await axios.post(
          'https://api.yourdomain.com/refresh-token',
          {},
          { withCredentials: true } // 允许携带 Cookie(Refresh Token)
        );
        
        const newAccessToken = res.data.accessToken;
        saveTokens(newAccessToken); // 保存新 Token
        originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
        return api(originalRequest); // 重试原请求
      } catch (refreshError) {
        // 刷新失败,跳转到登录页
        clearTokens();
        window.location.href = '/login';
        return Promise.reject(refreshError);
      }
    }
    
    return Promise.reject(error);
  }
);

export default api;
  • 后端
    *
    1. 登录接口:签发双 Token
      1. 刷新接口:用 Refresh Token 换新 Access Token
      1. 受保护接口:验证 Access Token
      1. 退出接口:吊销 Refresh Token
js 复制代码
// server.js
const express = require('express');
const jwt = require('jsonwebtoken');
const cookieParser = require('cookie-parser');
const app = express();

app.use(express.json());
app.use(cookieParser());

const ACCESS_TOKEN_SECRET = 'your_access_secret';
const REFRESH_TOKEN_SECRET = 'your_refresh_secret';

// 模拟数据库存储 Refresh Token
const refreshTokens = {};

// 1. 登录接口
app.post('/login', (req, res) => {
  const { username, password } = req.body;

  // 模拟用户验证
  if (username === 'admin' && password === '123456') {
    const userId = 1;
    
    // 生成 Access Token(15分钟过期)
    const accessToken = jwt.sign(
      { userId, role: 'admin' },
      ACCESS_TOKEN_SECRET,
      { expiresIn: '15m' }
    );

    // 生成 Refresh Token(7天过期)
    const refreshToken = jwt.sign(
      { userId },
      REFRESH_TOKEN_SECRET,
      { expiresIn: '7d' }
    );

    // 存 Refresh Token 到数据库
    refreshTokens[userId] = refreshToken;

    // 返回 Access Token,Refresh Token 通过 Cookie 设置
    res.cookie('refreshToken', refreshToken, {
      httpOnly: true,
      secure: true, // 生产环境启用 HTTPS
      maxAge: 7 * 24 * 60 * 60 * 1000, // 7天
    });

    res.json({ accessToken });
  } else {
    res.status(401).send('用户名或密码错误');
  }
});

// 2. 刷新 Token 接口
app.post('/refresh-token', (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  if (!refreshToken) return res.sendStatus(401);

  // 验证 Refresh Token
  jwt.verify(refreshToken, REFRESH_TOKEN_SECRET, (err, decoded) => {
    if (err || !refreshTokens[decoded.userId]) {
      return res.sendStatus(403); // 无效或已吊销
    }

    // 生成新 Access Token
    const newAccessToken = jwt.sign(
      { userId: decoded.userId, role: 'admin' },
      ACCESS_TOKEN_SECRET,
      { expiresIn: '15m' }
    );

    res.json({ accessToken: newAccessToken });
  });
});

// 3. 受保护接口(验证 Access Token)
app.get('/user/profile', (req, res) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) return res.sendStatus(401);

  jwt.verify(token, ACCESS_TOKEN_SECRET, (err, user) => {
    if (err) return res.sendStatus(403); // Token 无效或过期
    res.json({ name: '张三', role: user.role });
  });
});

// 4. 退出接口
app.post('/logout', (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  if (!refreshToken) return res.sendStatus(204);

  // 从数据库删除 Refresh Token
  jwt.verify(refreshToken, REFRESH_TOKEN_SECRET, (err, decoded) => {
    if (!err && decoded.userId) {
      delete refreshTokens[decoded.userId];
    }
  });

  // 清除 Cookie
  res.clearCookie('refreshToken');
  res.sendStatus(204);
});

app.listen(3000, () => console.log('Server running on port 3000'));

最后 手写防抖

js 复制代码
function debounce(func, delay) {
  let timer = null; // 用于存储定时器ID
  
  return function (...args) {
    // 清除上次的定时器
    if (timer) {
      clearTimeout(timer);
    }
    
    // 设置新的定时器
    timer = setTimeout(() => {
      func.apply(this, args); // 调用目标函数
    }, delay);
  };
}

总结

通过以上提问,我们可以看出大厂面试基本不考察纯粹的八股了,更加注重八股下面的底层原理,同时对实际开发中需要面对的场景问题进行深入的提问!所以在平时的学习过程中我们要多去研究源码,理解底层执行原理!

相关推荐
橘子味的冰淇淋~5 分钟前
【解决】Vue + Vite + TS 配置路径别名成功仍爆红
前端·javascript·vue.js
利刃之灵10 分钟前
03-HTML常见元素
前端·html
mysql学习中14 分钟前
数仓面试内容
面试·职场和发展
kidding72316 分钟前
gitee新的仓库,Vscode创建新的分支详细步骤
前端·gitee·在仓库创建新的分支
听风吹等浪起20 分钟前
基于html实现的课题随机点名
前端·html
leluckys25 分钟前
flutter 专题 六十三 Flutter入门与实战作者:xiangzhihong8Fluter 应用调试
前端·javascript·flutter
kidding72339 分钟前
微信小程序怎么分包步骤(包括怎么主包跳转到分包)
前端·微信小程序·前端开发·分包·wx.navigateto·subpackages
微学AI1 小时前
详细介绍:MCP(大模型上下文协议)的架构与组件,以及MCP的开发实践
前端·人工智能·深度学习·架构·llm·mcp
Java知识库1 小时前
Java BIO、NIO、AIO、Netty面试题(已整理全套PDF版本)
java·开发语言·jvm·面试·程序员
liangshanbo12151 小时前
CSS 包含块
前端·css