0%

参考数据迁移,不停机上线的正确姿势

staging slot 切换

新建staging库,从旧数据库同步数据,同步数据结束后在空闲时段停服,将流量切换至新库

不停机

  1. 准备新库 表结构更新或分库分表操作
  2. 开启双写,服务端同时向新老两个库写入数据 此阶段需要联查历史数据进行写入的操作可以先查老数据库 再写入新库
  3. 双写过程中,开始历史数据迁移,将某一时间点的历史数据迁移到新库,迁移范围要覆盖到双写数据范围以避免数据遗漏
  4. 数据校验,确认没有遗漏 以及写入失败的个别情况
  5. 开启双读,流量逐步过渡到新库
  6. 关闭老库写功能
  7. 删除双写双读等业务无关逻辑

Get和Post的区别


总结:get 用于获取信息,无副作用,幂等,且可缓存。
post 用于修改服务器上的数据,有副作用,非幂等,不可缓存

其实HTTP协议本身并没有对URL和BODY的长度限制,对URL限制的大多是浏览器和服务端自己限制的。

传参方式不受TCP传输限制 Get使用url Post使用Body的方式是约定俗成 服务端可自行制定规则从header或body获取参数

幂等性 (Idempotence)

Put vs Post

Data URL

最初见于css插入图片资源 data协议数据格式形如

1
data:[<mediatype>][;base64],<data>

其中mediatype 是个 MIME 类型的字符串,例如 ‘image/jpeg’ 表示 JPEG 图像文件。如果被省略,则默认值为 text/plain;charset=US-ASCII。

先说结论:性能优化是证据链,不是技巧清单

资深工程师的性能能力,不是能背出多少压缩、缓存、懒加载和防抖技巧,而是能回答一条完整证据链:谁在什么设备和场景下遇到了什么问题,时间或资源消耗在哪里,哪项改动为什么可能有效,复测结果如何,以及怎样防止回归。

Vibe Coding 降低了运行 Lighthouse、生成分析脚本、比较 bundle 和尝试优化代码的成本,但没有降低错误归因的代价。AI 可以快速提出 memo、虚拟列表、Web Worker 或拆包方案,却无法只凭源码知道真实用户是否受影响、当前瓶颈是不是主因、性能预算来自什么业务目标,以及优化是否破坏正确性、无障碍和可维护性。

因此,性能工作的默认顺序是:先定义体验和场景,再测量;先找到主导瓶颈,再优化;最后用同条件复测和回归守卫证明结果。

三层掌握标准

层级 必须掌握或会用 验证方式 AI 的位置
必须闭卷掌握 关键渲染路径的实用模型、主线程阻塞、网络与缓存、style/layout/paint、内存生命周期、测量先于优化 能从一个用户症状提出分层排查路径,并解释每项证据能排除什么 不能替代工程判断
会使用工具验证 DevTools Performance/Network/Memory、Lighthouse、Web Vitals/RUM、bundle 分析、Vue/React Devtools 保存可复现步骤、trace、请求瀑布、堆快照和前后数据 可帮助执行与摘要,原始证据必须可回看
可由 AI 初步完成 批量审计、trace/bundle 摘要、source map 热点报告、实验脚本、候选假设和报告初稿 人工抽查输入、工具版本、统计口径和结论,并独立复跑关键场景 加速机械工作,不负责最终因果结论

“会用 AI 做性能优化”至少意味着能审查它的测量环境、假设和改动范围。没有 trace、profile 或用户数据支持的优化建议,只能算待验证假设。

指标体系:从 Core Web Vitals 到业务体验

当前 Core Web Vitals 是 LCP、CLS 和 INP

  • LCP(Largest Contentful Paint)关注主要内容何时呈现,但“最大内容”是否等于用户真正等待的业务信息,需要结合页面确认。
  • CLS(Cumulative Layout Shift)关注非预期布局偏移。主动交互后的合理变化与无提示跳动不能混为一谈。
  • INP(Interaction to Next Paint)关注一次访问期间交互响应的整体表现。INP 已取代 FID 成为 Core Web Vital;FID 可能仍出现在历史看板中,但不应再当作当前核心指标。

通用参考常以第 75 百分位下 LCP 不高于 2.5 秒、CLS 不高于 0.1、INP 不高于 200 毫秒作为“良好”区间。这些值适合作为起点,不是所有产品的 SLA。项目预算还要写清用户分群、设备、网络、路由、操作流程、采样窗口和百分位。

想回答的问题 候选信号 主要限制
服务端或连接是否拖慢首屏 TTFB、请求瀑布、缓存命中、Server-Timing TTFB 正常不能证明资源加载和渲染正常
用户何时看到第一批内容 FCP 可能只是骨架或无业务价值的内容
主要内容何时出现 LCP 需要确认 LCP 元素与业务主内容一致
页面是否发生意外跳动 CLS、Layout Shift 记录 分数不能直接解释是哪一处布局策略导致
交互是否迟钝 Field INP、交互 trace、Event Timing 必须结合具体交互、处理时间和下一帧呈现分析
初始加载期间主线程是否拥堵 TBT、Long Task、Performance trace TBT 是 Lab 中与阻塞相关的代理,不等同于 Field INP
动画或 Web3D 是否流畅 frame time、掉帧、CPU/GPU 时间、draw call 平均 FPS 会掩盖长尾卡顿,也不能代表输入响应
页面是否随时间退化 JS heap、DOM/监听器数量、GPU 资源、队列长度 内存上升可能是缓存或延迟回收,需要持续观测与对照
核心任务是否真的更快 搜索完成、设备上线、模型加载、命令确认等业务指标 必须定义起点、终点、失败和取消语义

不要把不同指标拼成一个“越低越好”的列表。指标首先服务于问题:首屏慢、交互慢、滚动卡、运行半小时后变慢,所需证据并不相同。

Field、Lab 与 Lighthouse 分别回答什么

Field / RUM:真实用户发生了什么

真实用户监控(RUM)能够按版本、路由、设备、网络和用户分群观察分布,回答问题是否普遍、影响谁、是否随发布变化。公开 CrUX 数据也属于 Field 数据来源,但覆盖粒度和样本条件未必等同于产品自己的 RUM。

Field 数据的限制是环境变量多、定位细节少。看到某个版本 INP p75 变差,只能说明需要调查,不能直接证明某个组件或函数是原因。

Lab:能否稳定复现和解释

Lab 测试固定构建、数据、设备与网络条件,适合录制 trace、检查请求瀑布、比较改动前后和做 CI 回归。初始加载型合成测试常用 TBT 观察主线程阻塞;针对真实交互还需要执行相同操作并查看 Event Timing 和渲染过程。

单次 Lab 结果容易受预热、缓存、后台进程、浏览器版本和网络噪声影响。重要结论应多次运行,保留分布或至少中位数,并确保前后条件一致。

Lighthouse:综合审计,不是最终裁判

Lighthouse 提供受控环境下的性能、无障碍和最佳实践审计,适合发现候选问题、建立基线和自动化比较。总分由多个指标和权重合成,会随版本和配置变化;一次高分不能证明真实用户的关键流程没有性能问题,一次低分也不能直接定位根因。

可靠的回归门槛应绑定明确场景和环境,例如固定页面、登录态、数据量、设备模拟与运行次数。Lighthouse 可以是其中一个信号,但不能替代 RUM、业务指标和 Performance trace。

一套可复用的性能诊断流程

1. 定义用户问题和操作路径

把“页面很卡”改写成可执行场景,例如“设备详情页首次进入时三维模型长时间空白”或“报警列表滚动时点击确认延迟明显”。记录入口、账号状态、数据量、操作步骤和期待结果。输出是一条任何人都能重复的用户旅程。

2. 固定实验条件

记录构建版本、浏览器版本、设备、窗口尺寸、网络与 CPU 模拟、缓存冷热、数据种子和后端状态。若比较前后版本时环境变了,差异可能来自噪声而不是代码。输出是测试配置和可重复命令。

3. 建立基线并保存原始证据

根据症状保存 Performance trace、Network HAR、Lighthouse 报告、堆快照、RUM 切片或业务时间戳。只在文档里写“优化前 3 秒”不够,后续无法确认测量口径。输出是基线数据与原始文件位置。

4. 找到主导等待或工作

优先解释最大的用户等待:服务端响应、关键资源、JavaScript Long Task、框架更新、布局绘制、GPU 提交还是队列积压。不要把工具列出的所有警告同时修一遍,否则无法判断哪项改动有效。输出是一个按证据排序的瓶颈列表。

5. 写出可证伪假设

例如:“点击后的 320 ms Long Task 主要来自同步解析 8 MB JSON;若把解析改为分块并减少无用字段,交互处理时间应明显下降,网络时间不变。”假设必须包含预期变化和可能反证。输出是一项最小实验,而不是一组泛化最佳实践。

6. 做最小相关改动

只修改足以验证假设的边界,并保留功能、错误路径和无障碍行为。不要顺手升级框架、改状态库和重写组件,否则变量过多。输出是可独立审查的 diff。

7. 同条件复测正确性与性能

重复相同场景和运行次数,比较分布、trace 和业务结果。平均值变好但长尾变差、报警丢失或交互语义改变,都不算成功。输出是前后对照与结论边界。

8. 建立回归守卫

重要收益进入性能预算、自动化场景或 RUM 告警。预算可以针对资源大小、Long Task、业务耗时、内存稳态或关键交互,不必全部压缩成 Lighthouse 总分。输出是失败条件、执行环境和负责人。

分层定位:网络、主线程、渲染与内存

层级 主要证据 常见原因 候选动作 主要代价
服务端、网络与缓存 TTFB、HAR、Server-Timing、缓存头、连接复用 后端慢、串行请求、缓存失效、跨区网络 服务端优化、并行化、缓存/CDN、减少往返 一致性、失效策略、可观测性和运维成本
资源加载 请求优先级、coverage、bundle/source map、LCP 资源 首屏 JavaScript 过大、字体或图片阻塞、错误预加载 路由/组件拆分、压缩、图片尺寸、preload/preconnect 请求数量、缓存碎片、占位与代码复杂度
JavaScript 与主线程 flame chart、Long Task、Event Timing、Bottom-Up 同步解析、长循环、第三方脚本、过量序列化 分块、减少工作、调度、必要时 Worker 数据复制、通信、取消和调试成本
框架更新与 DOM Vue/React Profiler、组件更新原因、DOM 数量 高频状态发布、对象身份不稳、深响应、大列表 批量快照、稳定 props、浅状态、确认瓶颈后虚拟化 状态一致性、代码复杂度、可访问性
style/layout/paint/composite Rendering 面板、Layout Shift、Paint、图层 布局抖动、大面积绘制、非合成动画、DOM 读写交错 批量读写、减少影响范围、选择合成友好属性 图层内存、视觉取舍、浏览器差异
内存与生命周期 Heap snapshot、allocation timeline、DOM/监听器/订阅计数 未清理监听、定时器、缓存、闭包、Detached DOM 明确所有权、卸载清理、有界缓存、弱引用仅在合适语义下使用 清理时序、重新加载、缓存命中率
Canvas/WebGL/GPU frame time、Performance、renderer.info、draw call、GPU 工具 每帧分配、对象/材质过多、过绘制、资源未释放 复用、实例化、裁剪/LOD、按需渲染、dispose 视觉质量、实现复杂度、GPU/驱动差异

一些常见结论必须带条件:

  • HTTP/2 支持多路复用,不代表任意拆包都免费,也不代表应把所有代码重新合成一个包;仍要看缓存、优先级、解析和执行成本。
  • Web Worker 可以移开主线程工作,但序列化、数据复制、调度、错误和取消都有成本。先确认主线程计算确实是瓶颈。
  • 虚拟列表只减少大量列表项的 DOM/渲染成本,无法修复昂贵的数据解析、错误的响应式更新或 WebGL 掉帧。
  • memocomputed 和缓存可能减少重复计算,也可能增加比较、内存和失效复杂度;必须用 profiler 证明命中场景。

工业状态看板与 Web3D 性能

工业状态看板通常同时包含高频数据、关键报警、长时间运行和三维场景。它不能套用“每条消息立即 setState”或“全部节流丢弃”的统一策略。

数据入口与 UI 发布分离

  • WebSocket/ROS bridge 数据先做运行时校验、时间戳和设备 ID 归一化,再进入有界缓冲。
  • 遥测位姿可以按设备保留最新值,UI 按固定节奏发布快照;人眼不需要逐条 DOM 更新。
  • 报警、命令结果和审计事件不能与位姿采用同样的覆盖策略,需要可靠保留、确认或回放。
  • 队列到达上限时必须有显式过载策略和指标,不能让内存无限增长。

框架层保持稳定边界

大型外部对象、Three.js 实例和只读快照不应默认进入深响应式代理。Vue 可在合适边界使用 shallowRef/markRaw,React 可用稳定引用和选择器减少无关更新;选择依据仍是 profiler,而不是框架偏好。

Web3D 控制每帧成本和资源生命周期

  • 复用 Vector3、矩阵、BufferAttribute 等临时对象,避免热路径每帧制造大量垃圾。
  • 共享或实例化重复的 geometry/material,结合视锥裁剪和 LOD 控制 draw call 与顶点/像素工作量。
  • 静止场景可评估按需渲染;持续动画则应把数据采集、状态发布和渲染帧率解耦。
  • Three.js 从场景移除对象通常不会自动释放 GPU 资源,确认所有权后调用 geometry、material、texture、render target 等对象的 dispose()
  • 使用 renderer.info 和应用层资源计数观察趋势,但不要把单个计数当作跨设备统一 GPU 诊断结论。

示例验收合同

以下数字只是项目定义示例,不是行业标准:

1
2
3
4
5
场景:5,000 台模拟设备,每台每秒 10 条遥测,持续 30 分钟
约束:指定桌面设备与浏览器版本,固定数据种子
验收:队列不持续增长;交互 Long Task 与 INP 分布不劣化;
页面内存进入稳态;报警不被采样丢弃;
renderer.info 与应用资源计数不持续增长

测试还应保留固定输入或事件日志,能够回放一次卡顿和一次断连。没有可重复数据,现场性能问题很容易退化成“偶尔很卡”的主观争论。

Vibe Coding 时代怎样让 AI 参与性能工作

AI 可以自动化 工程师必须决定 必须保留的证据
批量运行 Lighthouse、整理版本差异 哪条用户旅程值得优化、环境是否代表真实用户 配置、工具版本、原始报告和运行分布
汇总 trace、Long Task 和 source map 热点 这些热点是否是主因,是否值得改架构 原始 trace、调用栈、时间占比和复现步骤
比较 bundle、重复依赖和资源大小 拆分/移除对缓存、功能和团队维护的影响 bundle 报告、加载瀑布和真实路由数据
生成 Playwright 场景、采样脚本和性能报告初稿 测试预言、预算、统计口径和发布门槛 可运行脚本、输入数据和失败样本
提出 memo、虚拟化、Worker、缓存等实验 因果假设、正确性、无障碍和复杂度取舍 预测指标、最小 diff、前后测量和反证

若 AI 只读源码就建议“加 memo + 虚拟列表 + Worker”,不要立刻实施。先要求它指出支持建议的 trace 片段、主线程占比、组件更新次数或 DOM 规模,并写出“若假设成立,哪个指标会变化”。没有证据时,把建议登记为候选实验即可。

适合交给智能体的闭环是:执行固定场景、收集工具产物、生成初步摘要、标出异常区段和制作对比表。人仍需回看原始数据、排除测量污染、确认业务正确性,并对最终取舍负责。

面试准备:讲诊断和取舍,不背固定答案

资深性能题可以统一使用:症状 -> 证据 -> 主导原因 -> 实验 -> 结果 -> 回归守卫

面试场景 回答重点 不完整回答
首次加载很慢 分解 TTFB、关键资源、解析执行、LCP 元素和缓存;区分冷/热缓存与用户分群 “上 CDN、gzip、懒加载”
INP 较差 找到具体交互,拆分输入延迟、处理和下一帧呈现,检查 Long Task 与更新范围 “加防抖”
滚动或动画卡顿 查看 frame time、主线程、layout/paint、对象分配和 GPU 工作 “用 transform 就好”
内存持续增长 固定操作循环,对比堆快照、Detached DOM、监听器、缓存和 GPU 资源趋势 “调用 GC”
Web3D 掉帧 区分 CPU 更新、draw call、顶点/像素负载、纹理和资源抖动 “降低模型面数”
Field 变差但 Lab 正常 分群设备/网络/路由/版本,补齐真实交互和长会话场景 “Lighthouse 是绿的,所以没问题”
如何证明优化有效 同条件多次运行、业务正确性、分布和回归门槛,并说明噪声与限制 “分数从 80 到 95”

面试时更有价值的是拿出一次失败实验:原以为 Worker 会改善交互,但数据复制抵消收益,最终通过减少输入规模解决。它证明你会证伪,而不是只会包装成功案例。

实践任务:工业状态看板性能治理

交付物

  • 一份明确用户旅程、数据量、设备和网络条件的基线报告。
  • 可回看的 Performance trace,以及一个网络假设和一个运行时假设。
  • 两项最小实验、前后数据和至少一项未达到预期的实验记录。
  • 资源、Long Task、业务耗时或内存稳态中的性能预算与回归命令。
  • 一段五分钟面试复盘:症状、证据、取舍、结果和限制。

验收标准

  • 优化前后使用同一构建方式、场景、数据和测量口径。
  • 页面业务正确性、报警可靠性和无障碍行为没有被性能优化破坏。
  • 报告链接到原始 trace/报告,不只保留截图和总结。
  • 所有阈值标记为本项目预算,并说明设备、样本和百分位。
  • 至少记录一个因为证据不足、收益过小或复杂度过高而放弃的优化。

面试价值

证明能够系统定位网络、主线程、框架、内存和 Web3D 问题,并用实验而不是术语数量推动决策。这类证据也能直接服务工业可视化和机器人调试台岗位。

性能预算和回归测试的测试层级见 前端测试策略与常用工具

暂时不死磕什么

  • 不背所有浏览器渲染流水线阶段和内部函数名,能从 trace 识别关键关系即可。
  • 不记每个 Lighthouse 版本的指标权重和评分曲线,工具升级时查官方文档。
  • 不把某个 Core Web Vitals 阈值、FPS 或 bundle 大小写成所有项目的“最佳值”。
  • 不在没有 profile 的情况下死磕闭包、循环或语法层面的微优化。
  • 不为了追求分数移除业务必要功能、无障碍语义、日志和错误处理。
  • 不同时使用大量性能工具却没有一条可复现用户旅程。

自检清单

  • [ ] 能区分 LCP、CLS、INP、TBT、Long Task、帧率、内存和业务指标解决的问题。
  • [ ] 能解释 Field/RUM、Lab 和 Lighthouse 的证据边界。
  • [ ] 能从用户症状建立固定场景,保存 trace,并写出可证伪假设。
  • [ ] 能判断瓶颈主要在网络、主线程、框架更新、布局绘制、内存还是 WebGL。
  • [ ] 能说明 Worker、虚拟化、缓存和 memo 何时可能无效或得不偿失。
  • [ ] 能审查 AI 的输入、工具版本、原始证据、因果结论和代码取舍。
  • [ ] 能拿出一组同条件的前后数据、一个失败实验和一条回归守卫。
  • [ ] 能在面试中说明哪些数字来自真实用户、Lab、仿真或项目假设。

参考资料

优先保留项目自己的用户数据、原始 trace 和复现脚本。工具文章和优化清单只能帮助提出假设,不能替代针对当前系统的证据。

在你的应用中安装sdk向Azure Application Insight服务发送遥测数据,支持web客户端,web服务和后台服务等
对应用性能影响小。非阻塞,单独线程

The instrumentation monitors your app and directs the telemetry data to an Azure Application Insights Resource using a unique GUID that we refer to as an Instrumentation Key. You can instrument not only the web service application, but also any background components, and the JavaScript in the web pages themselves. The application and its components can run anywhere - it doesn’t have to be hosted in Azure. 使用Guid作为Instrumentation Key(ikey)标识作为监视器的Azure Insights资源。 监测web服务应用或后台组件亦或页面js 这些应用或组件、命令不必托管在Azure中

创建Azure App Insights资源

Azure Portal -> Application Insights -> Create

添加Javascript SDK

1
npm i --save @microsoft/applicationinsights-web

使用ikey或连接字符串创建客户端instance

1
2
3
4
5
6
7
8
9
import { ApplicationInsights } from '@microsoft/applicationinsights-web'

const appInsights = new ApplicationInsights({ config: {
instrumentationKey: 'YOUR_INSTRUMENTATION_KEY_GOES_HERE' /* 使用ikey */
/* 或使用 connectionString: 'YOUR_CONNECTION_STRING_GOES_HERE' */
/* ...Other Configuration Options... */
} });
appInsights.loadAppInsights();
appInsights.trackPageView(); // Manually call trackPageView to establish the current user/session/pageview

React Plugin
1
2
npm install @microsoft/applicationinsights-react-js
npm install @microsoft/applicationinsights-web

创建实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// AppInsights.js
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { ReactPlugin } from '@microsoft/applicationinsights-react-js';
import { createBrowserHistory } from 'history';

const browserHistory = createBrowserHistory({ basename: '' });
const reactPlugin = new ReactPlugin();
const appInsights = new ApplicationInsights({
config: {
instrumentationKey: 'YOUR_INSTRUMENTATION_KEY_GOES_HERE',
extensions: [reactPlugin],
extensionConfig: {
[reactPlugin.identifier]: { history: browserHistory }
}
}
});
appInsights.loadAppInsights();
export { reactPlugin, appInsights };

在AppComponent使用Context.Provider 使所有子组件内可使用 useContext hook调用AppInsights实例
1
2
3
4
5
6
7
8
9
10
11
import React from "react";
import { AppInsightsContext } from "@microsoft/applicationinsights-react-js";
import { reactPlugin } from "./AppInsights";

const App = () => {
return (
<AppInsightsContext.Provider value={reactPlugin}>
/* your application here */
</AppInsightsContext.Provider>
);
};

子组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from "react";
import { useAppInsightsContext } from "@microsoft/applicationinsights-react-js";

const MyComponent = () => {
const appInsights = useAppInsightsContext();

appInsights.trackMetric("Component 'MyComponent' is in use");
appInsights.trackEvent({ name: 'Component Init', properties: { 'mydata' } });

return (
<h1>My Component</h1>
);
}
export default MyComponent;

useTrackMetric

1
2
3
4
5
6
7
8
const MyComponent = () => {
const appInsights = useAppInsightsContext();
const trackComponent = useTrackMetric(appInsights, "MyComponent");

return (
<h1 onHover={trackComponent} onClick={trackComponent}>My Component</h1>
);
}

useTrackEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React, { useState, useEffect } from "react";
import { useAppInsightsContext, useTrackEvent } from "@microsoft/applicationinsights-react-js";

const MyComponent = () => {
const appInsights = useAppInsightsContext();
const [cart, setCart] = useState([]);
const trackCheckout = useTrackEvent(appInsights, "Checkout", cart);
const trackCartUpdate = useTrackEvent(appInsights, "Cart Updated", cart);
useEffect(() => {
trackCartUpdate({ cartCount: cart.length });
}, [cart]);

const performCheckout = () => {
trackCheckout();
// submit data
};

return (
<div>
<ul>
<li>Product 1 <button onClick={() => setCart([...cart, "Product 1"])}>Add to Cart</button></li>
<li>Product 2 <button onClick={() => setCart([...cart, "Product 2"])}>Add to Cart</button></li>
<li>Product 3 <button onClick={() => setCart([...cart, "Product 3"])}>Add to Cart</button></li>
<li>Product 4 <button onClick={() => setCart([...cart, "Product 4"])}>Add to Cart</button></li>
</ul>
<button onClick={performCheckout}>Checkout</button>
</div>
);
}

export default MyComponent;

Click Analytics Auto-collection plugin

自动跟踪网页上的单击事件,并使用 HTML 元素上的 data-* 属性来填充事件遥测数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { ApplicationInsights } from '@microsoft/applicationinsights-web';
import { ClickAnalyticsPlugin } from '@microsoft/applicationinsights-clickanalytics-js';

const clickPluginInstance = new ClickAnalyticsPlugin();
// Click Analytics configuration
const clickPluginConfig = {
autoCapture: true
};
// Application Insights Configuration
const configObj = {
instrumentationKey: "YOUR INSTRUMENTATION KEY",
extensions: [clickPluginInstance],
extensionConfig: {
[clickPluginInstance.identifier]: clickPluginConfig
},
};

const appInsights = new ApplicationInsights({ config: configObj });
appInsights.loadAppInsights();

监视对象

  • User 用浏览器cookie中存储的匿名id区分用户
    JavaScript SDK 自动生成匿名用户和会话 ID,然后在从应用发送这些 ID 后使用这些 ID 填充遥测事件。
  • Session 不活动半小时重新记Session 活动24h后重新记Session
  • Event 每次执行trackEvent逻辑 Event参数已加入一组标准属性,包括匿名用户id(anonymous user ID)QQs存疑!

    其他

    cookie处理 visit time

简写为SOLID

单一职责(Sigle Responsibility Principle)

避免设计对象(类,方法)承担多项职责,在对职责1操作或修改时可能会造成职责2的异常。

  • 在需要if else时考虑划分为两个类或方法
  • 在需求变更导致职责细分时考虑将类或方法划分

开放关闭(Open Closed Principle)

设计对象应该根据需求被扩展,而不应该因需求而做修改,即对类进行抽象和继承
违反开放关闭原则:

1
2
3
4
5
6
7
8
9
10
11
class Factory {
public Computer produceComputer(String type) {
Computer c = null;
if(type.equals("macbook")){
c = new Macbook();
}else if(type.equals("surface")){
c = new Surface();
}
return c;
}
}

抽象出produce接口
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Factory {
public Computer produceComputer();
}
class AppleFactory implements Factory {
public Computer produceComputer() {
return new Macbook();
}
}
class MSFactory implements Factory {
public Computer produceComputer() {
return new Surface();
}
}

里氏替换(Liskov Substitution Principle)

所有引用基类的地方必须能透明地使用其子类的对象。

A的子类B继承父类时,不应改变原功能,只做扩展

接口隔离(Interface Segregation Principle)

合理设计接口的粒度,避免类型依赖它不需要的接口

依赖倒置(Dependency Injection Principle)

高层模块不应依赖低层模块,以免因为低层修改而牵扯高层,两者应该共同依赖接口,将改动限制在接口的实现上
即最少知道,类型对其直接引用的对象A保持最少的了解,不会通过该对象与第三者对象B建立间接的调用关系,要求A将必要的方法封装为public提供外部调用,而不暴露其引用了B的事实

23种设计模式

  • 触发器是一类存储过程
  • 由数据表的事件(如insert update delete)触发,而不是手动调用

登录触发器

官网例子 场景:如果登录名login_test 已经创建了三个用户会话,触发器将拒绝该用户的登录尝试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
USE master;  
GO
CREATE LOGIN login_test WITH PASSWORD = N'3KHJ6dhx(0xVYsdf' MUST_CHANGE,
CHECK_EXPIRATION = ON;
GO
GRANT VIEW SERVER STATE TO login_test;
GO
CREATE TRIGGER connection_limit_trigger
ON ALL SERVER WITH EXECUTE AS N'login_test'
FOR LOGON
AS
BEGIN
IF ORIGINAL_LOGIN()= N'login_test' AND
(SELECT COUNT(*) FROM sys.dm_exec_sessions
WHERE is_user_process = 1 AND
original_login_name = N'login_test') > 3
ROLLBACK;
END;

可知登录触发器在身份认证之后,建立会话之前触发
多个触发器的顺序,即支持指定the first和the last 见Microsoft Docs

DDL

data define language 数据定义语言
即在使用会改变数据库数据结构的语句时触发,如CREATE、ALTER、DROP、GRANT、DENY、REVOKE 或 UPDATE STATISTICS 开头的 Transact-SQL 语句
场景

  • 防止对数据库架构进行某些更改。
  • 希望数据库中发生某种情况以响应数据库架构的更改。
  • 记录数据库架构的更改或事件。

    DML

    data manipulation language (DML) 数据操作语言 INSERT、UPDATE 或 DELETE 语句
    after 触发器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    CREATE TRIGGER schemaA.SyncData 
    ON schemaA.TableA
    AFTER INSERT
    AS
    BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.

    -- Insert statements for trigger here
    insert into schemaB.TableB(Name,TrustedId,EmailAddress,Logo,Type,Enable,RecordStatus,RecordCreated,RecordLastUpdated)
    select name,record_pk,email,logo,'1',0,0,SYSDATETIME(),SYSDATETIME() from schemaA.TableA
    SET NOCOUNT ON;
    END
    GO
    instead of 触发器

  1. DOCTYPE 有什么作用?怎么写?
    1
    <!DOCTYPE html>
    html5规范的文档声明,示意浏览器以相应的标准解析文档,使支持html5规范如新的标签等
  2. 列出常见的标签,并简单介绍这些标签用在什么场景?
    canvas
  3. 页面出现了乱码,是怎么回事?如何解决?
    1
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
    可能是即字符编码不匹配造成
  4. title 属性和 alt 属性分别有什么作用?
  5. HTML 的注释怎样写?
  6. data- 属性的作用?
    自定义数据属性data-*
    1
    2
    var box = document.getElementById("box"); // <div id="box" data-user-name="QQs"></div>
    var username = box.dataset.userName;
  7. \ 的 title 和 alt 有什么区别?
  8. Web 标准以及 W3C 标准是什么? web标准
  9. HTML 全局属性(Global Attribute)有哪些?
    id class style title name data-* contenteditable translate
  10. meta 有哪些常见的值?charset
  11. meta viewport 是做什么用的,怎么写?\
  12. 列出常见的标签,并简单介绍这些标签用在什么场景?
  13. 如何在 HTML 页面上展示
    这几个字符?
  14. 你是如何理解 HTML 语义化的?
  15. 前端需要注意哪些 SEO?

VBoxManage

ubuntu可ssh远程用此命令行工具管理虚拟机

  • VBoxManage list vms/runningvms
  • VBoxManage startvm MyUbuntu
    1
    vboxmanage startvm MyUbuntu --type headless #在宿主机端隐藏图形界面 
  • VBoxManage controlvm MyUbuntu poweroff

OData和GraphQL

用了微软家的OData,就不得不再看一遍GraphQL,两者都让前端调用api获得了很大的自由度
相比OData的底层渗透性,GraphQL只在Http接口层做文章,实际上将底层模型封装成schema,开放给接口,这个过程更大程度上做到了安全性和前端业务的可控。

Quick Start

见官方GraphQL Doc:各种语言实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
var express = require('express');
var {graphqlHTTP} = require('express-graphql');
var { buildSchema } = require('graphql');

var schema = buildSchema(`
type Query {
heros: [Hero]
battle(name:String): String
random: Float!
}
type Hero{
name:String
abilities: [String]
}
`);

var root = { heros: () => ([
{
name: 'Luke skywalker',
abilities: ['light sward', 'force']
},
{
name: 'Anakin skywalker',
abilities: ['light sward', 'force', 'dark']
}
]),
battle: (name) => {
return 'I am your father';
},
random: () => {
return Math.random();
}
};

var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(3000, () => console.log('Now browse to localhost:3000/graphql'));

关于graphqlHTTP(也就是GraphiQL客户端)的Options:
schema是查询涉及的类型声明, rootValue api的查询方法的集,详见下文章节

schema 类型声明

1
2
3
4
5
6
7
var schema = buildSchema(`
type Hero {
name: String!
abilities: [String!]!
length(unit: LengthUnit = METER): Float
}
`);

标量类型

  • ID
  • String
  • Boolean
  • Int Float
    注意 !表示非空
    枚举
    1
    2
    3
    4
    5
    enum Episode {
    NEWHOPE
    EMPIRE
    JEDI
    }

接口和实现接口的类型

1
2
3
4
5
6
7
8
type Human implements Character {
id: ID!
name: String!
friends: [Character]
appearsIn: [Episode]!
starships: [Starship]
totalCredits: Int
}

联合类型

1
union SearchResult = Human | Droid | Starship

操作类型

  • query 查询:获取数据、查找
  • mutation 变更:对数据进行变更,比如增加、删除、修改
  • substription 订阅:当数据发生更改,进行消息推送

GraphQL client

前面的express-graphQL启动后打开GraphiQL页面,GraphiQL就是一个客户端,使用GraphQL client向 GraphQL 服务器上的入口端点发送一个 HTTP POST 请求,其中将 GraphQL 查询作为 JSON 载荷的 query 字段,就能调用 GraphQL 服务器。
其js实现大致是

1
2
3
4
5
6
7
8
9
10
fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({query: "{ hello }"})
})
.then(r => r.json())
.then(data => console.log('data returned:', data));

查询参数

schema 中的 Query声明了若干查询方法,查询方法的具体实现在root中定义,其参数类型的指定格式与typescript相同!
调用格式如下

1
2
3
{
battle(name:"dark lord")
}