2024-11-08 08:00:00
最近在做「Next.js 中文文档」的自动化工作和代码升级,比较忙,所以这篇文章就长话短说,把博客/文档类网站集成搜索功能的方法介绍一下。
差不多半年前,我发布了一个开源的博客模板,那时候我尝试使用 FlexSearch 来实现搜索。虽然开发的时候觉得很牛,但是真正去测试才发现这里有不少问题,主要是中文分词和拉丁语系不同,要想使用 FlexSearch 做出很好用的搜索效果,必须自己集成分词库,实践下来开发成本不低,那时候我只在模板里放了一个简化版的搜索,使用体验并不好。
因为搜索功能使用频率太低了,所以我后来就没怎么研究了。直到和阿伟一起开发 Next.js 中文文档的时候,阿伟给文档站集成了 Algolia,我才重新准备修改博客模板的搜索功能,现在终于花5分钟上线了新的搜索功能。
Algolia 有付费和免费两种,免费的是 DocSearch,只要网站的公开可访问的,都可以免费使用。
这篇文章的目的就是介绍一下集成 DocSearch 的方法。
收到邮件表示 Algolia 已经索引好了我们的网站,现在就可以集成了。注意圈起来的参数,后面开发的搜索框组件需要使用这三个核心参数。
安装依赖
pnpm i @docsearch/css @docsearch/react
创建 config/docSearchConfig.ts 定义配置:
/**
* 免费申请 algolia 文档搜索功能:https://docsearch.algolia.com/apply/
*/
interface DocSearchSiteConfig {
docSearch: {
appId: string;
indexName: string;
apiKey: string;
};
}
export const docSearchConfig: DocSearchSiteConfig = {
docSearch: {
appId: "填写邮件收到的参数",
indexName: "填写邮件收到的参数",
apiKey: "填写邮件收到的参数",
},
}
创建 DocSearch 搜索框组件,components/DocSearch/index.ts
核心代码如下:
"use client";
import { docSearchConfig } from "@/config/docSearch";
import "@docsearch/css";
import { DocSearchModal, useDocSearchKeyboardEvents } from "@docsearch/react";
import Link from "next/link";
import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { IoIosSearch } from "react-icons/io";
import "./docSearch.css";
export default function CustomDocSearch() {
const { appId, indexName, apiKey } = docSearchConfig.docSearch;
const [isOpen, setIsOpen] = useState(false);
const [isMac, setIsMac] = useState(false);
const searchButtonRef = useRef<HTMLButtonElement>(null);
const onOpen = useCallback(() => {
setIsOpen(true);
}, [setIsOpen]);
const onClose = useCallback(() => {
setIsOpen(false);
}, [setIsOpen]);
useDocSearchKeyboardEvents({
isOpen,
onOpen,
onClose,
searchButtonRef,
});
// 添加检测操作系统的效果
useEffect(() => {
setIsMac(navigator.platform.toUpperCase().indexOf("MAC") >= 0);
}, []);
return (
<>
<button className="docSearch-btn" data-variant="large" onClick={onOpen}>
搜索文档<kbd>{isMac ? "⌘K" : "Ctrl+K"}</kbd>
</button>
<button className="docSearch-btn" data-variant="medium" onClick={onOpen}>
搜索<kbd>{isMac ? "⌘K" : "Ctrl+K"}</kbd>
</button>
<button
className="docSearch-btn mr-2 hover:bg-accent border border-gray-300"
data-variant="small"
onClick={onOpen}
>
<IoIosSearch />
</button>
{isOpen &&
createPortal(
<DocSearchModal
initialScrollY={window.scrollY}
appId={appId}
apiKey={apiKey}
indexName={indexName}
onClose={onClose}
placeholder="搜索文档"
hitComponent={({ hit, children }) => (
<Link href={hit.url}>{children}</Link>
)}
/>,
document.body
)}
</>
);
}
docSearch.css
是自定义的搜索样式。
再把搜索框组件 CustomDocSearch
引入 Header 就可以使用了。
DocSearch 的搜索和集成都非常方便,现在我的博客模板、博客、信息差周刊都已经集成好了,这三个项目都是开源的,你可以直接复制我的组件代码。
我是一名全栈工程师,Next.js 开源手艺人,AI降临派。
今年致力于 Next.js 和 Node.js 领域的开源项目开发和知识分享。
欢迎在以下平台关注我:
2024-10-22 08:00:00
本文是 Next.js 官方发布的 15 版本博客翻译。
Next.js 15 正式发布并可用于生产环境。本次发布基于 RC1 和 RC2 的更新。我们在保证稳定性的同时,添加了一些令人兴奋的更新。现在就试试 Next.js 15:
# 使用新的自动升级 CLI
npx @next/codemod@canary upgrade latest
# ...或手动升级
npm install next@latest react@rc react-dom@rc
我们也很高兴能在本周四 (10 月 24 日) 的 Next.js Conf 上分享更多即将推出的内容。
以下是 Next.js 15 的新特性:
@next/codemod
CLI:轻松升级到最新的 Next.js 和 React 版本。fetch
请求、GET
路由处理程序和客户端导航默认不再缓存。unstable_after
API (实验性): 在响应完成流式传输后执行代码。instrumentation.js
API (稳定版): 用于服务器生命周期可观测性的新 API。next/form
): 使用客户端导航增强 HTML 表单。next.config
: next.config.ts
的 TypeScript 支持。Cache-Control
头部有更多控制。@next/codemod
CLI 平滑升级我们在每个主要的 Next.js 版本中都包含了 codemods (自动代码转换),以帮助自动处理破坏性变更的升级。
为了使升级更加平滑,我们发布了一个增强的 codemod CLI:
npx @next/codemod@canary upgrade latest
该工具帮助你将代码库升级到最新的稳定版或预发布版本。CLI 将更新你的依赖,显示可用的 codemods,并指导你应用它们。
canary
标签使用最新版本的 codemod,而 latest 指定 Next.js 版本。即使你正在升级到最新的 Next.js 版本,我们也建议使用 canary 版本的 codemod,因为我们计划根据你的反馈继续对该工具进行改进。
了解更多关于 Next.js codemod CLI 的信息。
在传统的服务器端渲染中,服务器会等待请求后再渲染任何内容。但是,并非所有组件都依赖于请求特定的数据,因此没有必要等待请求来渲染它们。理想情况下,服务器应该在请求到达之前尽可能多地准备。为了实现这一点,并为未来的优化奠定基础,我们需要知道何时等待请求。
因此,我们正在将依赖于请求特定数据的 API (如 headers
、cookies
、params
和 searchParams
) 转换为异步。
import { cookies } from 'next/headers';
export async function AdminPanel() {
const cookieStore = await cookies();
const token = cookieStore.get('token');
// ...
}
这是一个破坏性变更,影响以下 API:
cookies
headers
draftMode
params
(在 layout.js
、page.js
、route.js
、default.js
、generateMetadata
和 generateViewport
中)searchParams
(在 page.js
中)为了更容易迁移,这些 API 可以暂时同步访问,但在开发和生产环境中会显示警告,直到下一个主要版本。我们提供了一个 codemod 来自动完成迁移:
npx @next/codemod@canary next-async-request-api .
对于 codemod 无法完全迁移的情况,请阅读 升级指南。我们还提供了一个 示例 来说明如何将 Next.js 应用程序迁移到新的 API。
Next.js App Router 推出时具有独特的缓存默认值。这些默认值旨在默认提供最佳性能选项,同时在需要时可以选择退出。
根据你的反馈,我们重新评估了我们的 缓存启发式方法 以及它们如何与部分预渲染 (PPR) 和使用 fetch
的第三方库进行交互。
在 Next.js 15 中,我们将 GET
路由处理程序和客户端路由器缓存的默认值从默认缓存更改为默认不缓存。如果你想保留以前的行为,你可以继续选择启用缓存。
我们将在未来几个月继续改进 Next.js 中的缓存,并很快分享更多详细信息。
GET
路由处理程序默认不再缓存在 Next 14 中,使用 GET
HTTP 方法的路由处理程序默认会被缓存,除非它们使用动态函数或动态配置选项。在 Next.js 15 中,GET
函数默认不会缓存。
你仍然可以使用静态路由配置选项来选择启用缓存,比如 export dynamic = 'force-static'
。
特殊的路由处理程序如 sitemap.ts
、opengraph-image.tsx
、icon.tsx
和其他 元数据文件 默认仍然是静态的,除非它们使用动态函数或动态配置选项。
在 Next.js 14.2.0 中,我们引入了一个实验性的 staleTimes
标志,允许自定义配置 路由器缓存。
在 Next.js 15 中,这个标志仍然可用,但我们正在更改默认行为,将页面段的 staleTime
设为 0
。这意味着当你在应用程序中导航时,客户端将始终反映作为导航一部分而变为活动状态的页面组件的最新数据。然而,仍有一些重要的行为保持不变:
loading.js
将保持缓存 5 分钟(或 staleTimes.static
配置的值)你可以通过设置以下配置来选择使用之前的客户端路由器缓存行为:
// next.config.ts
const nextConfig = {
experimental: {
staleTimes: {
dynamic: 30
}
}
};
export default nextConfig;
作为 Next.js 15 发布的一部分,我们决定与即将发布的 React 19 保持一致。
在版本 15 中,App Router 使用 React 19 RC,根据社区反馈,我们还为使用 Pages Router 的 React 18 引入了向后兼容性。如果你使用 Pages Router,这允许你在准备好时升级到 React 19。
虽然 React 19 仍处于 RC 阶段,但我们在真实世界应用程序中的广泛测试以及与 React 团队的密切合作让我们对其稳定性充满信心。核心破坏性变更已经经过充分测试,不会影响现有的 App Router 用户。因此,我们决定现在将 Next.js 15 发布为稳定版,以便你的项目为 React 19 GA 做好充分准备。
为确保过渡尽可能顺利,我们提供了 codemods 和自动化工具 来帮助简化迁移过程。
阅读 Next.js 15 升级指南、React 19 升级指南 并观看 React Conf 主题演讲 以了解更多信息。
Next.js 15 保持了 Pages Router 与 React 18 的向后兼容性,允许用户在享受 Next.js 15 改进的同时继续使用 React 18。
从第一个发布候选版本(RC1)开始,根据社区反馈,我们将重点转向包含对 React 18 的支持。这种灵活性使你能够在使用 Pages Router 和 React 18 的同时采用 Next.js 15,让你对升级路径有更大的控制。
注意: 虽然在同一个应用程序中可以在 React 18 上运行 Pages Router、在 React 19 上运行 App Router,但我们不推荐这种设置。这样做可能会导致不可预测的行为和类型不一致,因为两个版本之间的底层 API 和渲染逻辑可能不完全一致。
React Compiler 是由 Meta 的 React 团队创建的一个新的实验性编译器。该编译器通过理解纯 JavaScript 语义和 React 规则 来深入理解你的代码,这使得它能够为你的代码添加自动优化。编译器减少了开发人员通过 useMemo
和 useCallback
等 API 进行手动记忆化的工作量 - 使代码更简单、更易维护且不易出错。
在 Next.js 15 中,我们添加了对 React Compiler 的支持。了解更多关于 React Compiler 和 可用的 Next.js 配置选项。
注意: React Compiler 目前仅作为 Babel 插件提供,这将导致开发和构建时间变慢。
Next.js 14.1 改进了 错误消息和 hydration 错误。Next.js 15 继续在此基础上添加了改进的 hydration 错误视图。Hydration 错误现在会显示错误的源代码以及如何解决问题的建议。
例如,这是 Next.js 14.1 中的 hydration 错误消息:
Next.js 15 改进后的效果:
我们很高兴地宣布 next dev --turbo
现在已经稳定并准备好加速你的开发体验。我们一直在使用它来迭代开发 vercel.com、nextjs.org、v0 和我们所有其他的应用程序,并取得了很好的效果。
例如,对于 vercel.com 这样的大型 Next.js 应用:
你可以在我们的新 博文 中了解更多关于 Turbopack Dev 的信息。
Next.js 现在在开发过程中显示静态路由指示器,帮助你识别哪些路由是静态的或动态的。这个可视化提示让你更容易理解页面的渲染方式,从而优化性能。
你还可以使用 next build 输出来查看所有路由的渲染策略。
这个更新是我们持续努力增强 Next.js 可观测性的一部分,使开发人员更容易监控、调试和优化他们的应用程序。我们也在开发专门的开发者工具,更多细节即将公布。
了解更多关于 静态路由指示器 的信息,该功能可以被禁用。
unstable_after
在响应后执行代码 (实验性)在处理用户请求时,服务器通常执行与计算响应直接相关的任务。但是,你可能需要执行一些任务,如日志记录、分析和其他外部系统同步。
由于这些任务与响应没有直接关系,用户不应该等待它们完成。但是在响应结束后延迟执行工作会带来挑战,因为无服务器函数会在响应关闭后立即停止计算。
after()
是一个新的实验性 API,通过允许你安排在响应流式传输完成后处理的工作来解决这个问题,使次要任务能够在不阻塞主要响应的情况下运行。
要使用它,请在 next.config.js
中添加 experimental.after
:
// next.config.ts
const nextConfig = {
experimental: {
after: true
}
};
export default nextConfig;
然后在服务器组件、服务器 Actions、路由处理程序或中间件中导入该函数:
import { unstable_after as after } from 'next/server';
import { log } from '@/app/utils';
export default function Layout({ children }) {
// 次要任务
after(() => {
log();
});
// 主要任务
return <>{children}</>;
}
了解更多关于 unstable_after
的信息。
instrumentation.js
(稳定版)带有 register()
API 的 instrumentation
文件允许用户接入 Next.js 服务器生命周期,以监控性能、追踪错误源头,并深度集成如 OpenTelemetry 等可观测性库。
这个功能现在已经稳定,可以移除 experimental.instrumentationHook
配置选项。
此外,我们与 Sentry 合作设计了一个新的 onRequestError
钩子,可用于:
export async function onRequestError(err, request, context) {
await fetch('https://...', {
method: 'POST',
body: JSON.stringify({ message: err.message, request, context }),
headers: { 'Content-Type': 'application/json' },
});
}
export async function register() {
// 初始化你喜欢的可观测性提供商 SDK
}
了解更多关于 onRequestError
函数 的信息。
<Form>
组件新的 <Form>
组件扩展了 HTML <form>
元素,增加了 预获取、客户端导航 和渐进增强功能。
它对于那些需要导航到新页面的表单很有用,比如导向结果页面的搜索表单。
// app/page.jsx
import Form from 'next/form';
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<button type="submit">提交</button>
</Form>
);
}
<Form>
组件具有以下特性:
之前,要实现这些功能需要大量的手动模板代码。例如:
// 注意: 这是为了演示目的而简化的代码。
// 不建议在生产代码中使用。
'use client'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
export default function Form(props) {
const action = props.action
const router = useRouter()
useEffect(() => {
// 如果表单目标是 URL,则预获取它
if (typeof action === 'string') {
router.prefetch(action)
}
}, [action, router])
function onSubmit(event) {
event.preventDefault()
// 获取所有表单字段并触发带有数据 URL 编码的 `router.push`
const formData = new FormData(event.currentTarget)
const data = new URLSearchParams()
for (const [name, value] of formData) {
data.append(name, value as string)
}
router.push(`${action}?${data.toString()}`)
}
if (typeof action === 'string') {
return <form onSubmit={onSubmit} {...props} />
}
return <form {...props} />
}
了解更多关于 <Form>
组件 的信息。
next.config.ts
Next.js 现在支持 TypeScript 的 next.config.ts
文件类型,并提供了 NextConfig
类型用于自动补全和类型安全的选项:
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
/* 在此处配置选项 */
};
export default nextConfig;
了解更多关于 Next.js 中的 TypeScript 支持。
在自托管应用程序时,你可能需要对 Cache-Control
指令有更多控制。
一个常见的情况是控制 ISR 页面发送的 stale-while-revalidate
周期。我们实现了两项改进:
next.config
中配置 expireTime
值。这之前是 experimental.swrDelta
选项。stale-while-revalidate
语义。我们也不再用默认值覆盖自定义的 Cache-Control
值,允许完全控制并确保与任何 CDN 设置兼容。
最后,我们改进了自托管时的图像优化。之前,我们建议你在 Next.js 服务器上安装 sharp
来优化图像。这个建议有时会被忽略。从 Next.js 15 开始,你不再需要手动安装 sharp
- 当使用 next start
或在 standalone 输出模式 下运行时,Next.js 将自动使用 sharp
。
要了解更多信息,请查看我们关于自托管 Next.js 的新 演示和教程视频。
Server Actions 是可以从客户端调用的服务器端函数。它们通过在文件顶部添加 'use server'
指令和导出异步函数来定义。
即使 Server Action 或工具函数没有在代码的其他地方导入,它仍然是一个可公开访问的 HTTP 端点。虽然这种行为在技术上是正确的,但可能导致这些函数被无意中暴露。
为了提高安全性,我们引入了以下增强功能:
// app/actions.js
'use server';
// 这个 action **被**用在我们的应用程序中,所以 Next.js
// 将创建一个安全的 ID 来允许客户端引用
// 和调用这个 Server Action。
export async function updateUserAction(formData) {}
// 这个 action **没有**在我们的应用程序中使用,所以 Next.js
// 将在 `next build` 期间自动移除这段代码
// 并且不会创建公共端点。
export async function deleteUserAction(formData) {}
你仍然应该将 Server Actions 视为公共 HTTP 端点。了解更多关于 保护 Server Actions 的安全。
打包外部包可以提高应用程序的冷启动性能。在 App Router 中,外部包默认会被打包,你可以使用新的 serverExternalPackages
配置选项来选择退出特定包。
在 Pages Router 中,外部包默认不会被打包,但你可以使用现有的 transpilePackages
选项提供要打包的包列表。使用此配置选项时,你需要指定每个包。
为了统一 App 和 Pages Router 之间的配置,我们引入了一个新选项 bundlePagesRouterDependencies
,以匹配 App Router 的默认自动打包。如果需要,你可以使用 serverExternalPackages
来选择退出特定包。
// next.config.ts
const nextConfig = {
// 在 Pages Router 中自动打包外部包:
bundlePagesRouterDependencies: true,
// 为 App 和 Pages Router 选择退出特定包的打包:
serverExternalPackages: ['package-name'],
};
export default nextConfig;
了解更多关于 优化外部包 的信息。
Next.js 15 还引入了对 ESLint 9 的支持,这是在 ESLint 8 于 2024 年 10 月 5 日结束生命周期后推出的。
为确保平稳过渡,Next.js 保持向后兼容,这意味着你可以继续使用 ESLint 8 或 9。
如果你升级到 ESLint 9,并且我们检测到你还没有采用 新的配置格式,Next.js 将自动应用 ESLINT_USE_FLAT_CONFIG=false
转义措施以便于迁移。
此外,在运行 next lint
时将移除已弃用的选项,如 --ext
和 --ignore-path
。请注意,ESLint 最终将在 ESLint 10 中禁用这些旧配置,所以我们建议你尽快开始迁移。
有关这些更改的更多详细信息,请查看 迁移指南。
作为此更新的一部分,我们还将 eslint-plugin-react-hooks
升级到 v5.0.0
,它为 React Hooks 使用引入了新规则。你可以在 [email protected] 的更新日志 中查看所有更改。
在开发过程中,保存时会重新执行服务器组件。这意味着,对 API 端点或第三方服务的任何 fetch
请求也会被调用。
为了提高本地开发性能并减少可能会产生费用的 API 调用,我们现在确保热模块替换(HMR)可以重用之前渲染的 fetch
响应。
了解更多关于 服务器组件 HMR 缓存 的信息。
我们优化了静态生成以改善构建时间,特别是对于具有慢速网络请求的页面。
此前,我们的静态优化过程会渲染页面两次 - 一次用于生成客户端导航的数据,第二次用于渲染初始页面访问的 HTML。现在,我们重用第一次渲染,减少了第二次渲染,从而减少工作量和构建时间。
此外,静态生成工作进程现在在页面之间共享 fetch
缓存。如果一个 fetch
调用没有选择退出缓存,其结果会被同一工作进程处理的其他页面重用。这减少了对相同数据的请求数量。
我们为那些需要更大控制权的高级用例添加了实验性支持,以便更好地控制静态生成过程。
我们建议坚持使用当前的默认值,除非你有特定的需求,因为这些选项可能会由于增加的并发性而导致资源使用增加和潜在的内存不足错误。
// next.config.ts
const nextConfig = {
experimental: {
// Next.js 在失败构建之前重试失败页面生成的次数
staticGenerationRetryCount: 1,
// 每个工作进程将处理的页面数量
staticGenerationMaxConcurrency: 8,
// 启动新的导出工作进程前的最小页面数
staticGenerationMinPagesPerWorker: 25
},
}
export default nextConfig;
了解更多关于 静态生成选项 的信息。
squoosh
转而支持 sharp
作为可选依赖 (PR)Content-Disposition
更改为 attachment
(PR)src
有前导或尾随空格时报错 (PR)react-server
条件以限制不推荐的 React API 导入 (PR)@next/font
包的支持 (PR)font-family
哈希 (PR)force-dynamic
现在会将 no-store
设为 fetch 缓存的默认值 (PR)swcMinify
(PR)、missingSuspenseWithCSRBailout
(PR) 和 outputFileTracing
(PR),并移除已弃用选项.xml
扩展名,并统一开发和生产环境之间的站点地图 URL (PR)export const runtime = "experimental-edge"
。用户现在应该切换到 export const runtime = "edge"
。我们添加了一个 codemod 来执行此操作 (PR)revalidateTag
和 revalidatePath
现在将抛出错误 (PR)instrumentation.js
和 middleware.js
文件现在将使用打包的 React 包 (PR)next/dynamic
: 已移除已弃用的 suspense
prop,并且当组件在 App Router 中使用时,它不会再插入空的 Suspense 边界 (PR)worker
模块条件 (PR)ssr: false
选项的 next/dynamic
(PR)metadataBase
的环境变量回退 (PR)optimizePackageImports
的混合命名空间和命名导入的树摇 (PR)bundlePagesExternals
现在稳定并重命名为 bundlePagesRouterDependencies
serverComponentsExternalPackages
现在稳定并重命名为 serverExternalPackages
.env
文件 (PR)outputFileTracingRoot
、outputFileTracingIncludes
和 outputFileTracingExcludes
已从实验性升级为稳定版 (PR)NEXT_CACHE_HANDLER_PATH
环境变量指定缓存处理程序 (PR)priority
属性 (PR)unstable_rethrow
函数来在 App Router 中重新抛出 Next.js 内部错误 (PR)unstable_after
现在可以在静态页面中使用 (PR)next/dynamic
组件,将会预取该块 (PR)esmExternals
选项 (PR)experimental.allowDevelopmentBuild
选项可用于允许调试目的下使用 NODE_ENV=development
运行 next build
(PR)vercel/og
更新了内存泄漏修复 (PR)msw
这样的包进行 API 模拟 (PR)要了解更多信息,请查看 升级指南。
我是一名全栈工程师,Next.js 开源手艺人,AI降临派。
今年致力于 Next.js 和 Node.js 领域的开源项目开发和知识分享。
欢迎在以下平台关注我:
2024-10-08 08:00:00
在我的全栈教程里,我分享了加密传输和保存用户 AI SDK key 的方案。这些方案属于全栈开发者必备知识,而且适用范围比较广,所以我决定提炼出来写成博客,希望能为更多开发者带来启发。
刚接触加密策略的时候,你可能会很懵,因为常见的加密方法太多了。我们可以把常见的加密方法分为三大类:散列算法、对称加密和非对称加密。
其中:
在密文不需要逆向回原始数据的场景下,使用散列算法是最好的,例如保存用户的账号密码的场景,我们可以用 MD5 加密传输并保存,当用户登录的时候,只要比较当前传输的 MD5 值和数据库保存的 MD5 值,二者相等即登录成功。因为散列算法的方式无法逆向,所以即使一个人拥有完整的代码权限、服务器权限、数据库权限,依然无法获得用户的密码。
而对于更多需要使用原始的场景,则是对称加密和非对称加密的主战场,其中又以 AES 和 RSA 加密方法出镜率最高。本文就以 AES 和 RSA 展开介绍如何加密传输和保存用户的隐私信息。
对称加密使用相同的密钥进行加密和解密,所以只要客户端与服务端定义一个相同的密钥即可互相传输密文,各自解密并使用。举个例子:
// 引入 CryptoJS 库
import CryptoJS from 'crypto-js';
// 加密
function encrypt(text, secretKey) {
return CryptoJS.AES.encrypt(text, secretKey).toString();
}
// 解密
function decrypt(ciphertext, secretKey) {
const bytes = CryptoJS.AES.decrypt(ciphertext, secretKey);
return bytes.toString(CryptoJS.enc.Utf8);
}
// 使用示例
const secretKey = 'mySecretKey123';
const plainText = 'Hello, AES!'; // 原始数据
const encrypt = encrypt(plainText, secretKey);
console.log('加密后:', encrypted);
const decrypt = decrypt(encrypted, secretKey);
console.log('解密后:', decrypted);
使用 AES 加密方法,服务端与客户端传输均使用 AES 加密后的密文。我们可以在客户端与服务端都创建这样一个文件,在调用加密方法 encrypt
的时候,传入原始数据和密钥,即可加密;在调用解密方法 decrypt
的时候,传入密文和密钥,即可获得原始数据。
但这种方式存在一个风险,如果密钥使用硬编码,很容易被破解,这样加密效果就会大打折扣。当然我们可以通过放在环境变量配置、经常更换密钥的方式来提升安全度。
那么有没有一种即使客户端保存的密钥被泄漏,依然可以保证数据安全的方式?有,这种方式就是非对称加密。
RSA(非对称加密)使用一对密钥:公钥和私钥。公钥可以公开分享,存放在客户端,用于数据传输前加密;私钥必须保密,存放在服务端,用于数据解密。这种方式解决了对称加密中密钥分发的问题。
crypto-js
库不支持 RSA 加密,我们可以选择使用 node-forge
或者 jsencrypt
进行 RSA 加密。
在使用 RSA 加密方法前,还需要先生成一对公钥和密钥。你可以打开控制台,在任意文件夹下执行下面两条命令:
# 生成私钥
openssl genrsa -out private_key.pem 2048
# 从私钥中提取公钥
openssl rsa -in private_key.pem -pubout -out public_key.pem
然后就会在这个文件夹下看到 private_key.pem
和 public_key.pem
两个文件,分别是私钥和公钥。
我们可以把公钥直接放在客户端;也可以把公钥和私钥一起存放在服务端,客户端需要公钥的时候向服务端请求获取公钥。
RSA 加密与私钥、公钥的的用法示例如下:
客户端加密数据:
import { JSEncrypt } from 'jsencrypt';
// 公钥
const publicKey = `
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7/Uu3...
-----END PUBLIC KEY-----`;
// 加密函数
function encrypt(text, pubKey) {
const encryptor = new JSEncrypt();
encryptor.setPublicKey(pubKey);
return encryptor.encrypt(text);
}
// 使用示例
const plainText = 'Hello, RSA!';
const encrypted = encrypt(plainText, publicKey);
console.log('加密后:', encrypted);
客户端加密数据时候,调用 encrypt
方法,传入原始数据和公钥即可完成加密。
服务端解密数据:
import { JSEncrypt } from 'jsencrypt';
// 私钥(实际应用中应该只存在于服务器)
const privateKey = `
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEA...
-----END PRIVATE KEY-----`;
// 解密函数
function decrypt(ciphertext, privKey) {
const decryptor = new JSEncrypt();
decryptor.setPrivateKey(privKey);
return decryptor.decrypt(ciphertext);
}
// 使用示例
const decrypted = decrypt(encrypted, privateKey);
console.log('解密后:', decrypted);
客户端加密数据时候,调用 decrypt
方法,传入密文和私钥即可完成解密。
举个实际场景,例如我们要保存用户的 AI SDK key。服务端收到 RSA 密文后,直接把密文存入数据库,当需要调用第三方 AI 接口的时候,再进行解密使用原始 key。
用非对称加密可以很安全地解决客户端向服务端传输数据和服务端使用数据的问题,但因为公钥加密过的信息只能用私钥解密,这就会造成另一个问题——客户端关闭再打开,从服务端获取到密文却无法直接解密,用户就无法知道保存过的数据是什么了。
仍然用保存用户 AI SDK key 的场景,问题描述就是:用户在设置页面想要查看保存的 key 原始数据时,靠客户端的公钥是做不到的。
解决这个问题也不难,思路有两个:
第一种方法仍然有数据泄漏的风险,我们一般不采用。第二种方法需要考虑既能从服务端加密传输到客户端,又能在客户端解密的需求,这就是我们下一个要解决的问题了。
上一节我们用 RSA 实现了客户端向服务端加密传输的功能,现在我们考虑一下,如何设计才能做出一个安全且方便的服务端向客户端加密传输的功能:
这里依然有一个加密与解密的功能,如果仍然用非对称加密,等于要把私钥放在客户端才行,那就没有意义了;所以这里应该用对称加密。而使用对称加密,我们就要保证密钥的安全。一阵推导之后,可以得出这样的方案:
aes_key
aes_key
,定义为 encry_aes_key
encry_aes_key
和密文发给服务端encry_aes_key
,得到 aes_key
aes_key
加密,然后把用 aes_key
加密的密文返回给客户端aes_key
对返回的密文进行解密// cryptoUtils.js
// 此处代码把客户端和服务端所需的方法合并到一个文件,只是为了讲解方便,实际开发需要分别创建
import CryptoJS from 'crypto-js';
import { JSEncrypt } from 'jsencrypt';
// 生成随机AES密钥
export function generateAESKey(length = 256) {
return CryptoJS.lib.WordArray.random(length / 8).toString();
}
// RSA加密
export function rsaEncrypt(text, publicKey) {
const encrypt = new JSEncrypt();
encrypt.setPublicKey(publicKey);
return encrypt.encrypt(text);
}
// RSA解密
export function rsaDecrypt(ciphertext, privateKey) {
const decrypt = new JSEncrypt();
decrypt.setPrivateKey(privateKey);
return decrypt.decrypt(ciphertext);
}
// AES加密
export function aesEncrypt(text, key) {
return CryptoJS.AES.encrypt(text, key).toString();
}
// AES解密
export function aesDecrypt(ciphertext, key) {
const bytes = CryptoJS.AES.decrypt(ciphertext, key);
return bytes.toString(CryptoJS.enc.Utf8);
}
// 将对象转换为JSON字符串并加密
export function encryptObject(obj, key) {
const jsonString = JSON.stringify(obj);
return aesEncrypt(jsonString, key);
}
// 解密JSON字符串并解析为对象
export function decryptObject(ciphertext, key) {
const jsonString = aesDecrypt(ciphertext, key);
return JSON.parse(jsonString);
}
// Base64编码
export function base64Encode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => {
return String.fromCharCode('0x' + p1);
}));
}
// Base64解码
export function base64Decode(str) {
return decodeURIComponent(atob(str).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
客户端使用示例:
import { generateAESKey, rsaEncrypt, aesEncrypt, aesDecrypt } from './cryptoUtils.js';
function sendEncryptedData() {
const aesKey = generateAESKey();
const encryptedAesKey = rsaEncrypt(aesKey, publicKey);
const data = { message: "Hello, Server!" };
const encryptedData = aesEncrypt(JSON.stringify(data), aesKey);
// 发送 encryptedAesKey 和 encryptedData 到服务器
// ...
// 处理服务器响应,encryptedResponse 为服务端返回的加密数据
const decryptedResponse = aesDecrypt(encryptedResponse, aesKey);
console.log(JSON.parse(decryptedResponse));
}
这样每次请求服务端都会生成一次 AES 密钥,而且密钥使用 RSA 加密,这样安全级别是足够的。
服务端使用示例:
import { rsaDecrypt, aesEncrypt, aesDecrypt } from './cryptoUtils.js';
function handleClientRequest(encryptedAesKey, encryptedData) {
const aesKey = rsaDecrypt(encryptedAesKey, privateKey);
const decryptedData = aesDecrypt(encryptedData, aesKey);
const data = JSON.parse(decryptedData);
// 处理解密后的数据
// ...
// 准备响应数据
const responseData = { result: "Success" };
const encryptedResponse = aesEncrypt(JSON.stringify(responseData), aesKey);
// 返回加密的响应给客户端
return encryptedResponse;
}
现在就完成了客户端和服务端双向加密传输的逻辑了。
实际开发中,加密方案可能由更多加密方案组合而成,需要根据业务需求进行设计。文中已经介绍了 AES、RSA、AES+RSA 三种加密策略的风险与安全范围,如果符合你的产品需求,那么可以直接套用以上方案,如果你的产品需要更高级别的安全策略,也希望本文的方案可以为你带来启发。
本文思路是从我的专栏「Chrome 插件全栈开发实战」提炼而来,专栏内容包括:
专栏现在已有60位读者,如果其中包含你想学习的知识,欢迎成为下一名读者,你将获得真实的出海项目开发讲解和专业的模块化设计的源码。
我是一名全栈工程师,Next.js 开源手艺人,AI降临派。
今年致力于 Next.js 和 Node.js 领域的开源项目开发和知识分享。
欢迎在以下平台关注我:
2024-10-05 08:00:00
如果你从其他网站看到这篇文章,请直接点击这里快速进入教程页面
大家好,我是程普,即刻App、微信公众号「BigYe程普」,推特「weijunext」。
前段时间,我发布了第一个出海产品 PH Copilot,是一个基于 Plasmo 开发的 Chrome 插件和 Next.js 开发的落地页与服务端的产品。
因为我之前围绕 Next.js 生态圈做了一些开源项目,使用过 next-auth、Lemonsqueezy、prisma 等技术栈,这一次就想尝试了一些没用过的技术栈,因为只有这样我接下来才有文章可写。
等开发完成后,我发现这一路学到的知识和踩过的坑,不是几篇零散的博客文章可以讲好的,所以我决定写一份系统性的教程,把 PH Copilot 的开发过程和使用的技术方案完整记录下来。于是就写了这套 「Chrome插件全栈开发实战」 教程。
这套教程包含 Chrome 插件开发和 Next.js 全栈开发的知识,教你最适合新人独立开发者的出海技术栈组合,帮助你半个月内容成为出海全栈工程师。
教程地址:https://xiaobot.net/p/ship-ph-copilot?refer=30e2865d-a056-4752-a4d7-b5ce6716c71e
专栏配套源码,购买专栏即额外获得一份 Chrome 插件源码和一份 Next.js SaaS 全栈源码。
教程和源码包含的主要技术知识:
对于国内独立开发者而言,这个技术栈组合最具性价比:
这套技术栈组合非常适合用来快速构建、验证和扩展 SaaS 产品。
当时开发插件的目的之一就是想写出海全栈产品开发教程,所以在写教程的时候,我对每一个功能进行 code review 和代码优化,然后编写开发教程,整个过程花费了3周。
教程从实践角度出发,从0开始构建项目、搭建Chrome插件模板、开发功能模块,每一个步骤都单独讲解,可以帮助你渐进式学习。
源码包含 Chrome 插件端和 Next.js 端两份代码,其中 Next.js 端包含落地页和服务端功能,所有核心功能做了模块化设计,你不仅可以获得源码,还能潜移默化学会专业的代码设计方式。
本专栏不是0基础入门教程,不提供基础的环境安装和开发工具使用指导,所以不适合 React/Next.js 技术0基础的朋友。
本专栏适合以下人群学习:
微信提供全程答疑服务,教到你学会。
如果你每天业余时间都可以用来学习,跟着教程的步骤,你可以在2周内学会全栈出海产品的开发。
为了防止恶意购买并退款,会在购买后48小时左右获得源码访问权限。
从文章开头的目录思维导图你可以了解到教程包含的技术栈和知识点;我还整理了代码结构,你可以透过代码结构知道源码模块化设计的程度有多高:
// 插件端代码结构(仅展示src部分)
ph-copilot-dev-guide
├─ src
│ ├─ background
│ │ ├─ firebase
│ │ │ ├─ authService.ts
│ │ │ ├─ config.ts
│ │ │ ├─ index.ts
│ │ │ ├─ tokenManager.ts
│ │ │ └─ userService.ts
│ │ ├─ aiResponseHandler.ts
│ │ ├─ index.ts
│ │ ├─ messageHandler.ts
│ │ ├─ sendMessageToSource.ts
│ │ ├─ storageManager.ts
│ │ └─ updateChecker.ts
│ ├─ components
│ │ ├─ LanguageSwitcher
│ │ │ ├─ LanguageContext.tsx
│ │ │ ├─ index.tsx
│ │ │ ├─ translations.ts
│ │ │ └─ useTranslation.ts
│ │ └─ ui
│ │ ├─ avatar.tsx
│ │ ├─ button.tsx
│ │ ├─ card.tsx
│ │ ├─ popover.tsx
│ │ ├─ select.tsx
│ │ ├─ toggle.tsx
│ │ └─ tooltip.tsx
│ ├─ contents
│ │ ├─ components
│ │ │ ├─ ContentCommentGenerator.tsx
│ │ │ ├─ ContentOverviewGenerator.tsx
│ │ │ └─ CopilotTools.tsx
│ │ ├─ hooks
│ │ │ ├─ useDOMObserver.ts
│ │ │ └─ useUrlChangeListener.ts
│ │ ├─ styles
│ │ │ └─ content.css
│ │ ├─ utils
│ │ │ └─ constants.ts
│ │ └─ index.tsx
│ ├─ lib
│ │ ├─ constant.ts
│ │ ├─ prefixByEnv.ts
│ │ ├─ useProductDetails.ts
│ │ └─ utils.ts
│ ├─ popup
│ │ ├─ components
│ │ ├─ hooks
│ │ ├─ styles
│ │ ├─ utils
│ │ └─ index.tsx
│ ├─ sidepanel
│ │ ├─ components
│ │ │ ├─ CommentCard.tsx
│ │ │ ├─ Header.tsx
│ │ │ ├─ OverviewCard.tsx
│ │ │ └─ UserInfoCard.tsx
│ │ ├─ hooks
│ │ ├─ styles
│ │ ├─ utils
│ │ └─ index.tsx
│ ├─ store
│ │ ├─ firebaseAuthStorage.ts
│ │ ├─ useCommentLength.ts
│ │ ├─ useLanguageStorage.ts
│ │ └─ useUserData.ts
│ ├─ types
│ │ ├─ product.ts
│ │ └─ user.ts
│ └─ style.css
~懂的人都知道这里面门道有多深~。(这是一个梗,勿喷)
点击链接或微信扫码即可购买:
教程地址:https://xiaobot.net/p/ship-ph-copilot?refer=30e2865d-a056-4752-a4d7-b5ce6716c71e
我是一名全栈工程师,Next.js 开源手艺人,AI降临派。
今年致力于 Next.js 和 Node.js 领域的开源项目开发和知识分享。
欢迎在以下平台关注我:
2024-10-01 08:00:00
Vue 作者尤雨溪宣布创立了 VoidZero 公司,该公司致力于为 JavaScript 生态系统构建开源、高性能和统一的开发工具链。该公司已获得由 Accel 领投的460万美元种子轮融资。
以下是尤雨溪发布的原文翻译:
15年前,当我开始用 JavaScript 构建应用时,它主要还是一种浏览器脚本语言。如今,它已发展成为世界上使用最广泛的语言,支持从网页和移动应用到游戏开发甚至物联网的各种场景。
多年来,许多优秀的工具应运而生,以应对日益增长的 JavaScript 应用规模和复杂性。然而,生态系统一直是分散的:每个应用都依赖于众多第三方依赖,将它们配置在一起仍然是开发周期中最棘手的任务之一。
作为一个被广泛使用的前端框架的作者,我花了大量精力研究 JavaScript 工具栈的每一层,组装了数百个依赖项,并在其上设计了复杂的抽象。目标始终是为最终用户提供一个连贯的、开箱即用的开发体验。这些努力最终导致了 2020 年 Vite 的诞生。
四年后的今天,Vite 已经成为最受欢迎的 Web 开发构建工具之一,每周下载量超过 1500 万次,拥有庞大的生态系统。除了成为使用 React 和 Vue 构建单页应用的首选外,Vite 还为 Remix、Nuxt、Astro、SvelteKit、SolidStart、Qwik、Redwood 等元框架提供支持。它显然已经成为下一代 Web 框架的共享基础设施层。
社区对 Vite 的信任让我深入思考了它的未来。虽然 Vite 大大改善了高层开发者体验,但在内部,它仍然依赖于各种依赖项,并采用抽象和变通方法来消除不一致性。从性能角度来看,它仍然受到不同工具之间重复解析和序列化成本的瓶颈,而且由于功能限制和可定制性有限,它无法充分利用 esbuild 等原生工具。
我们开始设计一个新的打包工具 Rolldown,以专门解决 Vite 的需求。但当我深入研究打包工具底层时,我意识到 Vite 面临的挑战其实反映了 JavaScript 生态系统的普遍问题:碎片化、不兼容和低效。要从根本上改变这一点,需要一个统一的工具链。
想象一下这样一个工具链:
这样的工具链不仅会增强 Vite,还会推动整个 JavaScript 生态系统的重大改进。这是一个雄心勃勃的愿景,实现它需要一个全职的专门团队——这在我过去项目的独立可持续模式下是不可能的。这就是成立 VoidZero 的原因。
我很高兴地宣布,我们已经筹集了 460 万美元的种子轮融资来追求这一愿景。我们的种子轮由 Accel 领投,Amplify Partners、Preston-Werner Ventures、BGZ、Eric Simons(StackBlitz)、Paul Copplestone(Supabase)、David Cramer(Sentry)、Matt Biilmann & Christian Bach(Netlify)、Dafeng Guo(Strikingly)、Sebastien Chopin(NuxtLabs)、Johannes Schickling(Prisma) 和 Zeno Rocha(Resend) 跟投。
在过去的一年里,我们组建了一个在 JavaScript 工具方面拥有深厚专业知识的团队,包括广泛采用的开源项目如 Vite、Vitest、Oxc 的创建者和核心贡献者,以及 Rspack 的前核心贡献者。
我们一直在努力开发我们设想的工具链的基础元素。除了对 Vite 的持续改进外,我们还提供了:
虽然还处于早期阶段,但我们的开源项目已经被一些世界领先的工程团队使用,包括 OpenAI(ChatGPT网页客户端)、Google、Apple、Microsoft、Visa、Shopify、Cloudflare、Atlassian、Reddit、HuggingFace、Linear 等等。
我们未来几个月的主要目标是稳定 Rolldown,并使其成为 Vite 在开发和生产中的统一打包工具。我们已经取得了很大进展,目标是在今年晚些时候发布由 Rolldown 驱动的 Vite 的 alpha 版本。
2025 年,我们将继续完成 Oxc 计划的其他功能(压缩、格式化),并逐步将整个 Vite 生态系统迁移到由 Rolldown 和 Oxc 驱动。我们将与生态系统合作伙伴和利益相关者密切合作,确保最终用户顺利过渡。
我们开源的所有内容都将保持开源。在我们的开源项目之上,我们将提供一个端到端的 JavaScript 工具解决方案,专门设计用于满足企业环境的规模和安全需求。
在 X 上关注我们以了解最新进展。如果你对这些工具有大规模需求,请联系我们!如果你有兴趣为我们的项目做出贡献或在其基础上构建,请加入我们的 Discord 服务器(Vite、Vitest、Oxc、Rolldown)。最后,别忘了本周收看 ViteConf,我们将在那里分享更多关于我们的进展和未来计划的细节。
Vite 和 Vitest 的团队治理模式保持不变。两个核心团队都包括来自多个不同组织(VoidZero、StackBlitz、NuxtLabs、Astro)雇佣的成员。VoidZero 公司雇佣/赞助 Vite 和 Vitest 的多个核心贡献者。
VoidZero 公司拥有 Oxc 和 Rolldown 的版权,资助其开发,并控制其方向。
VoidZero 作为一项业务与 Vue 完全分开。Vue 将继续作为一个独立项目,但将获得 VoidZero 开发的新工具的一流支持。
我们的许多团队成员过去都对 SWC 做出了重要贡献。除了原始性能优势外,Oxc 在基本设计上与 SWC 有一些根本性的不同,使其成为我们正在构建的端到端工具链的更好基础。我们将在未来的博客文章中分享更多技术见解。敬请关注!
我们需要一个极其快速、适合应用打包、并且与 Vite 的插件生态系统完全兼容的打包工具。这在 Rolldown 文档中有详细讨论。在 Oxc 之上构建 Rolldown 还解锁了在打包阶段并行执行更多 AST 相关任务的能力,例如用 isolatedDeclarations: true
发出和打包 dts。
统一工具链最大的挑战是从零到一的问题:它需要获得关键质量以实现指数级采用,以证明持续开发的合理性,但在实际实现愿景之前很难跨越鸿沟。VoidZero 没有这个问题,因为 Vite 已经是 JavaScript 生态系统中增长最快的工具链。
原文完结
我是一名全栈工程师,Next.js 开源手艺人,AI降临派。
今年致力于 Next.js 和 Node.js 领域的开源项目开发和知识分享。
欢迎在以下平台关注我: