面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 05月29日 00:52

如何在 Cypress 中用 cy.request() 测试 API 接口?

cy.request() 是 Cypress 内置的 HTTP 请求方法,直接在网络层发送请求,无需经过浏览器渲染,适合独立验证后端逻辑。典型场景:绕过 UI 直接测试 API 返回值和状态码、在 UI 测试前通过 API 预置测试数据(如登录获取 token)、验证认证头和权限控制。最佳实践包括:用 Cypress.env() 管理环境变量避免硬编码 URL、用 failOnStatus: false 测试错误响应、结合 cy.intercept() 模拟后端异常、用 response.duration 断言接口性能。注意 cy.request() 默认 4xx/5xx 会导致测试失败,测试异常场景时必须设置 failOnStatusCode: false。追问cy.request() 和 cy.intercept() 有什么区别?什么时候该配合使用?如何用 cy.request() 实现 UI 测试前的数据预置?比通过 UI 操作准备数据有什么优势?测试需要鉴权的 API 时,如何自动获取并注入 Bearer Token?cy.request() 发送的请求会走浏览器的 CORS 限制吗?为什么?如何对 cy.request() 的响应做 Schema 校验而不只是断言个别字段?写段代码// API 测试:登录 + 鉴权 + 错误场景it('returns 401 with wrong password', () => { cy.request({ url: '/api/login', method: 'POST', body: { user: 'admin', pass: 'wrong' }, failOnStatusCode: false }).its('status').should('eq', 401);});
服务端阅读 05月29日 00:52

Cypress 如何处理异步操作?命令链和自动等待机制是什么?

Cypress 的命令不是立即执行,而是入队后按序串行执行。每个命令返回 chainable 对象,后续命令挂载到链条上形成命令队列,Cypress 依次取出执行并自动等待前置条件满足。自动等待指每个命令内建重试机制:cy.get() 会反复查询 DOM 直到元素存在且可见,cy.request() 会等待响应返回,默认超时 4 秒。开发者无需写 sleep 或显式等待,Cypress 在命令间自动处理异步时序。追问命令队列和 Promise 链有什么区别?命令队列在 .then() 之前不会执行,是同步入队异步执行;Promise 链是立即执行。所以不能把 Cypress 命令赋值给变量:const el = cy.get('#btn') 拿到的是 chainable 不是元素,必须用 .then() 回调取值。什么时候需要用 .then()?需要访问命令返回值或混合同步逻辑时。比如从响应中提取 ID 再构造下一个请求。注意 .then() 内部的 cy 命令会重新入队,不会立即执行。自动等待超时了怎么办?可通过 { timeout: 10000 } 单独设置,或在 cypress.config.js 中配置 defaultCommandTimeout 全局调整。超时后命令失败,测试中断并截图。应优先用 should() 断言替代加大超时。cy.wait() 和自动等待什么时候用?自动等待覆盖 DOM 和 XHR 场景,一般够用。但 cy.intercept() 拦截请求后需 cy.wait('@alias') 确保请求完成再断言响应,这是显式等待的典型场景。为什么不能在 .then() 外用 async/await?Cypress 命令不在 Promise 上运行,await 一个 chainable 不会等命令执行完。混用 async/await 会导致时序错乱,Cypress 官方明确不推荐在命令链中使用 async/await。写段代码cy.intercept('GET', '/api/users').as('users');cy.visit('/dashboard');cy.wait('@users').its('response.statusCode').should('eq', 200);cy.get('#user-list').should('be.visible');
服务端阅读 05月29日 00:52

Cheerio 性能怎么优化?大文件和高并发场景怎么处理?

Cheerio 性能优化抓住三个方向:选择器、内存、并发。选择器方面:用 .find() 配合具体 class 替代深层后代选择器,缓存 $container 后链式调用避免重复查询。内存方面:大文件用 stream 分块解析代替一次 load,批量 DOM 操作先拼字符串再一次性 .html() 插入,用完的 $ 引用及时置空触发 GC。并发方面:多 URL 用 Promise.all 并行请求 + 逐个解析,超大数据集用 Worker 线程分片处理。load 选项中 decodeEntities: false 和 withDomLvl1: false 也能减少不必要的解析开销。追问为什么 .find() 比层级选择器快?$('.container .item .title') 每次都从根节点全量匹配三层;$('.container').find('.item').find('.title') 先锁定容器再在子集中查找,搜索范围逐层缩小。差距在元素数量大时(万级以上)才明显。大文件怎么避免内存溢出?不要 cheerio.load(wholeFile),改用 stream 按 </item> 等边界标签分割,每块单独 load 解析后立即释放。内存占用从 O(n) 降到 O(chunk),10MB 文件也不会爆。批量插入 DOM 为什么不能逐个 append?每次 .append() 都触发内部 DOM 树重建,1000 次就是 1000 次重建。正确做法是先用数组拼 HTML 字符串,最后 .html(str) 一次性写入,从 O(n) 次操作降到 1 次。load 选项哪些影响性能?decodeEntities: false 跳过 HTML 实体解码(不需要中文转义时关闭);withDomLvl1: false 跳过 DOM Level 1 兼容处理;normalizeWhitespace: false 跳过空白合并。三个都关掉可提速 15-20%。多 URL 并发爬取怎么做?用 p-limit 或手动分批 Promise.all:for (let i = 0; i < urls.length; i += 5) 每批 5 个并发,避免同时发起数百请求被限流或打挂目标服务器。写段代码// 流式解析大文件,内存恒定const results = [];let buf = '';fs.createReadStream('big.html') .on('data', chunk => { buf += chunk; const matches = buf.match(/<item[\s\S]*?<\/item>/g) || []; matches.forEach(m => { const $ = cheerio.load(m); results.push($('name').text()); }); buf = buf.slice(buf.lastIndexOf('</item>') + 7); });
服务端阅读 05月29日 00:52

Cypress 和 Selenium 有什么区别?何时选择 Cypress?

核心区别在架构:Cypress 运行在与应用同源的浏览器内,通过 Chrome DevTools Protocol 直接操作 DOM,内置自动等待和重试机制;Selenium 通过外部 WebDriver 进程与浏览器通信,需显式编写等待逻辑。这意味着 Cypress 调试体验远优于 Selenium(可视化 Test Runner、时间旅行),且代码更简洁,但仅支持 Chromium 内核和 JavaScript;Selenium 跨浏览器覆盖全面(Chrome/Firefox/Safari),支持多语言(Java/Python/C#),适合需要兼容性测试的团队。选择 Cypress 的场景:前端 SPA 项目为主、团队用 JavaScript、追求快速反馈和低维护成本。选 Selenium 的场景:必须覆盖多浏览器、团队非 JS 技术栈、需测试非 Web 应用。追问Cypress 的同源架构为什么无法测试跨域场景?有什么变通方案?Selenium 的显式等待(WebDriverWait)和隐式等待(implicit wait)有什么区别?各自的风险是什么?Cypress 的 cy.intercept() 如何模拟后端响应?与 Selenium 的 Mock Server 方案相比优劣如何?大型项目中 Cypress 测试执行变慢,如何优化?Playwright 与 Cypress 相比有哪些改进?是否正在取代 Cypress?写段代码// Cypress: 自动等待,无需 sleepcy.visit('/login');cy.get('#user').type('admin');cy.get('#pass').type('1234');cy.get('#submit').click();cy.url().should('include', '/dashboard');// Selenium (Python): 必须显式等待from selenium.webdriver.support.ui import WebDriverWaitelem = WebDriverWait(driver, 10).until( EC.element_to_be_clickable((By.ID, 'submit')))elem.click()
服务端阅读 05月29日 00:51

Elasticsearch 的 suggest 功能如何实现自动补全?

ES 提供四种 suggester:Completion Suggester 基于内存 FST(有限状态转换器)做前缀匹配,延迟极低,适合自动补全;Term Suggester 基于编辑距离做拼写纠错;Phrase Suggester 在 Term 基础上加 n-gram 语言模型优化整句建议;Context Suggester 为 Completion 增加分类/地理上下文过滤。自动补全场景用 Completion Suggester:索引时将建议词存入 completion 类型字段构建 FST,查询时用 prefix 参数匹配,毫秒级返回。追问Completion Suggester 为什么快?它将建议词构建为 FST 结构常驻内存,前缀匹配是 O(k) 复杂度(k 为前缀长度),不涉及倒排索引扫描和评分计算,所以延迟在毫秒级。Completion 和 searchasyou_type 有什么区别?Completion 是独立建议通道,返回建议词而非文档;searchasyoutype 是特殊 text 子类型,本质还是全文检索返回文档。Completion 更适合输入框补全,searchasyoutype 更适合边输边搜文档内容。拼写纠错用哪个?Term Suggester。它基于倒排索引中的词项做编辑距离计算,返回相似词建议。需要指定 suggest_mode:missing(仅缺词建议)、popular(热门词优先)、always(始终建议)。如何给建议词加权重?在 completion 字段中设置 weight 属性,权重高的建议优先返回。常用于热门搜索词提权:"suggest": {"input": "laptop", "weight": 100}。FST 内存开销大吗?FST 是高度压缩的有向无环图,百万级建议词通常只占几十 MB。但建议词总量达亿级时需评估内存,可用 context 过滤减少单次匹配范围。写段代码PUT /search-suggest{ "mappings": { "properties": { "title": { "type": "keyword" }, "suggest": { "type": "completion", "analyzer": "standard" } } }}GET /search-suggest/_search{ "suggest": { "auto": { "prefix": "elas", "completion": { "field": "suggest", "size": 5 } } }}
服务端阅读 05月29日 00:51

DevSecOps 的关键实践有哪些?如何将安全左移?

DevSecOps 是将安全内嵌到 DevOps 全流程的实践,核心理念是"安全左移"——在编码阶段而非上线后才做安全检查。关键实践包括:在 CI 流水线中集成 SAST(静态代码扫描)和 SCA(依赖漏洞扫描),构建阶段做容器镜像扫描(Trivy),部署前用 DAST 对运行时做动态测试,运行时通过 Falco 做入侵检测。此外还包括用 Vault 管理密钥与凭证轮换、RBAC 最小权限控制、基础设施即代码的安全扫描(tfsec),以及用 SLSA 框架保障软件供应链完整性。本质是把安全从"门卫"变成"内嵌检查点"。追问SAST 和 DAST 分别能发现什么类型的漏洞?为什么两者必须互补?容器镜像扫描应该扫基础镜像还是应用层?Trivy 的工作原理是什么?HashiCorp Vault 如何实现密钥自动轮换而不中断服务?SLSA 框架的四个级别分别保证什么?达到 Level 3 需要哪些前提?如何处理安全扫描的大量误报而不拖慢 CI 流水线?写段代码# CI 流水线内嵌安全扫描stages: - security - testsast: stage: security script: semgrep ci --config autodep-scan: stage: security script: snyk test --severity-threshold=highcontainer-scan: stage: test script: - docker build -t app:$CI_SHA . - trivy image --exit-code 1 app:$CI_SHA
服务端阅读 05月29日 00:51

如何开发 Cheerio 插件?有哪些实用插件模式?

Cheerio 插件的核心模式是扩展 cheerio.prototype(即 $.fn),给选择器结果集添加自定义方法。基本写法:module.exports = function(cheerio) { cheerio.prototype.myMethod = function() { return this; } },然后 cheerio.use(pluginFn) 加载。插件方法内部通过 this 访问当前选中的元素集合,用 cheerio(el) 包装后即可调用所有原生方法。返回 this 支持链式调用,返回 .get() 则输出普通数组。追问插件方法怎么访问当前选中的元素?方法内 this 就是 cheerio 对象,this.each((i, el) => ...) 遍历,cheerio(el) 包装单个元素。this.length 获取匹配数量,this[i] 直接取原生节点。怎么让插件支持配置参数?导出一个工厂函数而非直接函数:module.exports = (options) => (cheerio) => { ... },调用时 cheerio.use(myPlugin({ trim: true }))。内部用默认参数合并:const opts = { trim: true, ...options }。有哪些实用的插件场景?最常见三类:①文本清洗——批量 removeTags/cleanText,去掉 script/style/注释;②结构化提取——tableToArray 把表格转二维数组,tableToObjects 按表头生成对象数组;③URL 标准化——resolveUrls 把相对路径转绝对路径,处理懒加载 data-src。插件方法和独立工具函数怎么选?需要操作 DOM 节点、支持链式调用、依赖当前选择器上下文时用插件;纯数据转换、不涉及 DOM 操作时用普通函数。插件的优势是和 cheerio API 风格统一,缺点是污染原型。开发插件要注意什么?方法名加业务前缀避免冲突(如 seo_extractLinks 而非 extractLinks);返回 this 保持链式;用 try-catch 包裹核心逻辑防止异常中断;在 peerDependencies 声明 cheerio 版本。写段代码// 表格转对象数组插件module.exports = function(cheerio) { cheerio.prototype.tableToObjects = function() { const headers = this.find('th').map((i, el) => cheerio(el).text().trim()).get(); return this.find('tbody tr').map((i, tr) => { const obj = {}; cheerio(tr).find('td').each((j, td) => { obj[headers[j]] = cheerio(td).text().trim(); }); return obj; }).get(); };};
服务端阅读 05月29日 00:51

Elasticsearch 如何实现地理空间搜索?

ES 通过 geopoint 和 geoshape 两种类型支持地理搜索。geopoint 存储经纬度坐标点,支持 geodistance(圆形范围)、geoboundingbox(矩形范围)、geopolygon(多边形范围)查询;geoshape 存储复杂几何形状(线、多边形),支持相交、包含等空间关系查询。底层使用 geohash 编码将二维坐标映射为一维字符串,利用 BKD tree 索引加速范围检索。查询时先通过 geohash 前缀粗筛,再计算精确距离过滤。追问geopoint 和 geoshape 怎么选?存储门店位置等点数据用 geopoint,存储配送区域等面数据用 geoshape。geopoint 查询更快,geoshape 支持更复杂的空间关系但索引开销更大。geo_distance 查询性能如何优化?先用 geoboundingbox 缩小候选集,再在结果上做精确距离计算。也可设置 geopoint 的 geohashprecision 控制索引精度。经纬度顺序容易搞混怎么办?ES 的 geo_point 支持多种格式:字符串 "lat,lon"、数组 [lon,lat](GeoJSON 标准)、对象 {lat,lon}。数组格式是 lon 在前,容易出错,推荐用对象格式避免歧义。geohash 精度怎么选?精度越高定位越准但索引越大。常见选择:1km 精度用 geohash_precision=5(约 4.9km 边长),100m 用 6,10m 用 7。业务精度需求决定精度设置。geo 查询能和普通查询组合吗?可以,放在 bool query 的 filter 子句中。geo 查询不计算评分,适合做过滤条件配合全文检索使用。写段代码PUT /stores{ "mappings": { "properties": { "name": { "type": "keyword" }, "location": { "type": "geo_point" } } }}GET /stores/_search{ "query": { "bool": { "filter": { "geo_distance": { "distance": "5km", "location": { "lat": 39.9, "lon": 116.4 } } } } }}
服务端阅读 05月29日 00:51

持续集成、持续交付和持续部署有什么区别?

三者是递进关系:持续集成(CI)解决"代码能否合入"的问题——每次提交自动触发构建和测试,确保主分支始终可构建;持续交付(Continuous Delivery)解决"代码能否随时上线"的问题——在 CI 基础上自动化部署到预发布环境,但推送到生产环境需要人工审批门控;持续部署(Continuous Deployment)解决"代码能否自动上线"的问题——通过所有测试的变更直接部署到生产,无需人工干预。核心区别就在一个门:生产环境部署前是否有手动批准环节。选哪个取决于业务风险容忍度和测试覆盖成熟度。追问CI 中"每次提交都触发构建"在高频提交时如何避免流水线排队?持续交付中手动审批门控应该设在哪个环节?审批人需要关注什么?从持续交付升级到持续部署,测试覆盖率要达到什么水平才安全?蓝绿部署和金丝雀发布在持续部署中各起什么作用?CI/CD 流水线中安全扫描(SAST/DAST)应该放在哪个阶段?写段代码# GitHub Actions:CI + 持续交付流水线on: [push]jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci && npm test deploy-staging: needs: test runs-on: ubuntu-latest environment: staging # 自动部署到预发布 steps: - run: npx deploy-to staging
服务端阅读 05月29日 00:51

Cheerio 爬取动态页面为什么拿不到数据?怎么解决?

Cheerio 只是 HTML 解析器,不执行 JavaScript,所以通过 JS 动态渲染的内容(SPA、AJAX 加载、无限滚动)用 Cheerio 直接解析会拿到空壳 HTML。解决方案有三条路径:1) 用 Puppeteer/Playwright 先渲染页面拿到完整 HTML 再交给 Cheerio 解析;2) 拦截网络请求直接找到数据 API 端点,用 axios 请求 JSON;3) 分析页面源码中的内联数据(如 window.INITIAL_STATE)直接提取。追问方案 2(直接请求 API)相比方案 1 有什么优势?速度快、资源消耗低、无需启动浏览器。缺点是 API 可能有签名/鉴权,需要逆向分析请求参数。Puppeteer 和 Playwright 选哪个?Playwright API 更现代,原生支持多浏览器,自动等待机制更智能;Puppeteer 生态更成熟、Chrome 专项优化更好。新项目推荐 Playwright。如何优化 Puppeteer + Cheerio 方案的性能?拦截非必要资源(图片/字体/CSS)不加载,复用浏览器实例而非每次新建,设置合理的 waitForSelector 超时而非固定等待。怎么判断页面是动态渲染还是静态 HTML?curl 获取 HTML 后检查目标元素是否存在,如果 curl 拿不到但浏览器能看到,就是动态渲染。写段代码const puppeteer = require('puppeteer');const cheerio = require('cheerio');async function scrape(url) { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle2' }); const $ = cheerio.load(await page.content()); await browser.close(); return $('.item').map((i, el) => $(el).text()).get();}