乐闻世界logo
搜索文章和话题

服务端面试题手册

cURL 如何设置请求头(Headers)?

在 cURL 中,请求头(Request Headers) 用于传递元数据,如认证信息、内容类型等。正确设置请求头对于 API 调用至关重要。基本语法使用 -H 或 --header 参数添加请求头:curl -H "Header-Name: Header-Value" URL常用请求头示例# 设置 Content-Typecurl -H "Content-Type: application/json" \ https://api.example.com/users# 设置 Authorization(Bearer Token)curl -H "Authorization: Bearer your_token_here" \ https://api.example.com/protected# 设置 User-Agentcurl -H "User-Agent: MyApp/1.0" \ https://api.example.com/data# 设置 Accept(期望的响应格式)curl -H "Accept: application/json" \ https://api.example.com/users# 设置多个请求头curl -H "Content-Type: application/json" \ -H "Authorization: Bearer token123" \ -H "Accept: application/json" \ -H "X-Custom-Header: custom-value" \ https://api.example.com/users常见请求头类型| 请求头 | 用途 | 示例 || ------------- | --------- | --------------------------------------------------- || Content-Type | 指定请求体格式 | application/json, application/x-www-form-urlencoded || Authorization | 身份认证 | Bearer token, Basic auth || Accept | 期望的响应格式 | application/json, text/html || User-Agent | 客户端标识 | Mozilla/5.0, MyApp/1.0 || Cookie | 发送 Cookie | session_id=abc123 || Cache-Control | 缓存控制 | no-cache |特殊用法# 发送 Cookiecurl -H "Cookie: session_id=abc123; user_id=456" \ https://api.example.com/profile# 跨域请求curl -H "Origin: https://example.com" \ -H "Access-Control-Request-Method: POST" \ https://api.example.com/data# 压缩请求curl -H "Content-Encoding: gzip" \ --data-binary @compressed.gz \ https://api.example.com/upload# 从文件读取请求头curl -H @headers.txt https://api.example.com/users注意事项格式要求:请求头必须为 "Name: Value" 格式,冒号后有空格大小写:HTTP 头部名称不区分大小写,但建议遵循规范特殊字符:如果值包含空格或特殊字符,需要用引号包裹重复设置:多次使用 -H 设置同一头部,后者会覆盖前者
阅读 0·3月7日 19:36

cURL 如何处理 URL 编码和特殊字符?

在 cURL 中处理 URL 编码和特殊字符是常见需求,特别是在发送包含空格、中文或特殊符号的数据时。正确编码能确保请求被正确解析。URL 编码基础URL 编码(Percent Encoding)将特殊字符转换为 %XX 格式,其中 XX 是字符的十六进制 ASCII 码。# 需要编码的常见字符空格 -> %20 或 +& -> %26= -> %3D? -> %3F# -> %23% -> %25中文 -> %E4%B8%AD%E6%96%87cURL 的编码方式1. --data-urlencode 自动编码# 自动对数据进行 URL 编码curl -X POST https://api.example.com/search \ --data-urlencode "query=hello world"# 结果:query=hello%20world# 编码特殊字符curl -X POST https://api.example.com/search \ --data-urlencode "query=foo&bar=baz"# 结果:query=foo%26bar%3Dbaz# 编码中文字符curl -X POST https://api.example.com/search \ --data-urlencode "query=中文搜索"# 结果:query=%E4%B8%AD%E6%96%87%E6%90%9C%E7%B4%A22. 手动编码# 使用 printf 进行编码ENCODED=$(printf '%s' 'hello world' | od -An -tx1 | tr -d ' \n' | sed 's/../%&/g')curl -X POST -d "query=$ENCODED" https://api.example.com/search# 使用 Python 编码ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('hello world'))")curl -X POST -d "query=$ENCODED" https://api.example.com/search# 使用 jq 编码ENCODED=$(jq -nr --arg s 'hello world' '$s|@uri')curl -X POST -d "query=$ENCODED" https://api.example.com/searchURL 中的特殊字符处理查询参数中的特殊字符# 错误方式:& 会被解释为参数分隔符curl "https://api.example.com/search?q=foo&bar=baz"# 实际发送两个参数:q=foo 和 bar=baz# 正确方式:手动编码curl "https://api.example.com/search?q=foo%26bar%3Dbaz"# 或使用 --data-urlencode(自动处理 &)curl -G https://api.example.com/search \ --data-urlencode "q=foo&bar=baz"路径中的特殊字符# 路径中包含空格curl "https://api.example.com/files/my%20document.pdf"# 路径中包含中文curl "https://api.example.com/files/%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6.pdf"# 使用 --path-as-is 保留特殊路径curl --path-as-is "https://api.example.com/../secret.txt"不同场景的处理方式场景 1:表单数据提交# 使用 --data-urlencode 自动编码表单数据curl -X POST https://api.example.com/submit \ --data-urlencode "name=张三" \ --data-urlencode "email=zhangsan@example.com" \ --data-urlencode "message=Hello World! 你好世界!"# 混合使用编码和非编码数据curl -X POST https://api.example.com/submit \ -d "id=123" \ --data-urlencode "content=Special chars: & = ?"场景 2:URL 查询参数# 使用 -G 和 --data-urlencode 构建查询字符串curl -G https://api.example.com/search \ --data-urlencode "q=hello world" \ --data-urlencode "category=技术&编程" \ --data-urlencode "page=1"# 结果 URL:# https://api.example.com/search?q=hello%20world&category=%E6%8A%80%E6%9C%AF%26%E7%BC%96%E7%A8%8B&page=1场景 3:JSON 数据中的特殊字符# JSON 中的特殊字符需要转义curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d '{"name":"张三","bio":"Line1\nLine2\tTabbed"}'# 使用 jq 处理复杂 JSONjq -n '{name: "张三", bio: "Line1\nLine2"}' | \ curl -X POST https://api.example.com/users \ -H "Content-Type: application/json" \ -d @-编码工具函数# URL 编码函数url_encode() { local string="$1" local encoded="" local pos c o for (( pos=0; pos<${#string}; pos++ )); do c=${string:$pos:1} case "$c" in [-_.~a-zA-Z0-9]) encoded+="$c" ;; *) printf -v o '%%%02x' "'$c"; encoded+="$o" ;; esac done echo "$encoded"}# 使用示例QUERY=$(url_encode "hello world & more")curl "https://api.example.com/search?q=$QUERY"常见编码问题# 问题 1:空格被错误处理# 错误:curl "https://api.example.com/search?q=hello world"# 正确:curl "https://api.example.com/search?q=hello%20world"# 问题 2:& 符号被解释为分隔符# 错误:curl "https://api.example.com/search?q=A&B"# 正确:curl "https://api.example.com/search?q=A%26B"# 问题 3:中文乱码# 错误:curl "https://api.example.com/search?q=中文"# 正确:curl "https://api.example.com/search?q=%E4%B8%AD%E6%96%87"完整示例脚本#!/bin/bash# URL 编码处理示例API_BASE="https://api.example.com/v1"# 编码函数urlencode() { python3 -c "import urllib.parse; print(urllib.parse.quote('''$1'''))"}# 搜索请求search_query="hello world & special chars: 中文"encoded_query=$(urlencode "$search_query")echo "Original: $search_query"echo "Encoded: $encoded_query"# 发送请求curl -G "${API_BASE}/search" \ --data-urlencode "q=$search_query" \ -H "Accept: application/json"# 表单提交form_data="name=张三&email=test@example.com&msg=Hello World!"curl -X POST "${API_BASE}/submit" \ --data-urlencode "name=张三" \ --data-urlencode "email=test@example.com" \ --data-urlencode "msg=Hello World!"最佳实践优先使用 --data-urlencode:自动处理编码,避免错误查询参数使用 -G:配合 --data-urlencode 构建 URLJSON 使用工具生成:用 jq 处理复杂 JSON,自动转义避免手动拼接 URL:容易遗漏编码,导致请求失败测试编码结果:使用 -v 查看实际发送的 URL
阅读 0·3月7日 19:36

Solidity 中 storage、memory 和 calldata 三种数据位置的区别是什么?

在 Solidity 中,storage、memory 和 calldata 是三种不同的数据存储位置,理解它们的区别对于编写高效的智能合约至关重要。1. Storage(存储)特点:永久存储在区块链上,是合约状态变量的默认存储位置成本:最昂贵,需要消耗 Gas 来写入生命周期:永久存在,直到合约被销毁适用场景:需要持久化保存的数据,如用户余额、合约配置等contract Example { uint256 public storedData; // 默认 storage function updateData(uint256 _data) public { storedData = _data; // 写入 storage }}2. Memory(内存)特点:临时存储,函数执行完毕后自动释放成本:中等,比 storage 便宜生命周期:仅在函数执行期间存在适用场景:函数参数、局部变量、临时计算结果function processArray(uint256[] memory _arr) public pure returns (uint256) { uint256 sum = 0; for (uint i = 0; i < _arr.length; i++) { sum += _arr[i]; } return sum;}3. Calldata(调用数据)特点:只读区域,存储函数调用的输入数据成本:最便宜,不消耗额外 Gas生命周期:仅在函数执行期间存在适用场景:外部函数的引用类型参数(数组、结构体等)function externalFunction(uint256[] calldata _data) external pure returns (uint256) { // _data 是只读的,不能修改 return _data.length;}对比总结| 特性 | Storage | Memory | Calldata || ------ | ------- | ------ | -------- || 持久性 | 永久 | 临时 | 临时 || 可修改性 | 可读写 | 可读写 | 只读 || Gas 成本 | 高 | 中 | 低 || 适用场景 | 状态变量 | 局部变量 | 外部函数参数 |最佳实践优先使用 calldata:对于外部函数的引用类型参数,使用 calldata 可以节省 Gas避免不必要的 storage 操作:storage 操作昂贵,尽量减少写入次数内存管理:复杂计算时,合理使用 memory 避免频繁的 storage 访问数据拷贝注意:从 storage 复制到 memory 或 calldata 会消耗 Gas,注意优化
阅读 0·3月7日 19:35

OpenCV.js 中的 Mat 对象是什么,如何创建和管理?

OpenCV.js 中的 Mat(Matrix)是最核心的数据结构,用于存储图像和矩阵数据。Mat 的基本概念Mat 是一个 n 维数组,可以存储:单通道或多通道图像(灰度、RGB、RGBA)矩阵数据其他数值类型的数据创建 Mat 对象// 创建空 Matlet mat = new cv.Mat();// 创建指定大小的 Mat(默认黑色)let mat = new cv.Mat(rows, cols, type);// 常用类型cv.CV_8UC1 // 8位无符号单通道(灰度图)cv.CV_8UC3 // 8位无符号三通道(RGB图)cv.CV_8UC4 // 8位无符号四通道(RGBA图)cv.CV_32FC1 // 32位浮点单通道从 HTML 元素创建 Mat// 从 canvas 创建let canvas = document.getElementById('canvas');let mat = cv.imread(canvas);// 从 img 元素创建let img = document.getElementById('image');let mat = cv.imread(img);Mat 的常用操作// 读取和设置像素值let pixel = mat.ucharAt(row, col); // 读取单通道像素let pixel = mat.data; // 获取所有像素数据// 复制 Matlet matCopy = mat.clone();// 创建感兴趣区域(ROI)let roi = mat.roi(new cv.Rect(x, y, width, height));// 转换颜色空间cv.cvtColor(mat, mat, cv.COLOR_RGBA2GRAY);// 释放内存mat.delete();内存管理注意事项手动释放:JavaScript 没有自动垃圾回收机制,必须手动调用 delete() 释放内存避免内存泄漏:在不再使用 Mat 时立即释放使用 try-finally:确保异常情况下也能释放资源try { let mat = new cv.Mat(100, 100, cv.CV_8UC3); // 处理图像} finally { mat.delete();}常见错误// 错误:忘记释放内存导致内存泄漏let mat = new cv.Mat();// 使用后没有调用 mat.delete()// 错误:重复释放mat.delete();mat.delete(); // 会抛出错误// 正确:使用智能指针模式function processImage() { let mat = new cv.Mat(); try { // 处理逻辑 return mat.clone(); // 返回副本 } finally { mat.delete(); // 释放原始 Mat }}
阅读 0·3月7日 12:25

React Query 中的 useQuery 和 useMutation 钩子有什么区别,分别适用于什么场景?

React Query 中的 useQuery 和 useMutation 是两个核心钩子,它们的区别和适用场景如下:useQuery功能:用于执行只读操作,如获取数据。适用场景:获取列表数据(如用户列表、产品列表)获取详情数据(如用户详情、订单详情)任何需要从服务器读取数据而不修改的场景特点:自动缓存数据支持数据失效和背景刷新返回数据、加载状态、错误状态等可以配置重试策略基本用法:const { data, isLoading, error } = useQuery('todos', fetchTodos);useMutation功能:用于执行修改操作,如创建、更新、删除数据。适用场景:提交表单数据更新用户信息删除资源任何需要修改服务器数据的场景特点:支持乐观更新可以配置成功/失败回调支持请求取消可以触发相关查询的重新获取基本用法:const mutation = useMutation(addTodo, { onSuccess: () => { // 成功回调,如重新获取数据 queryClient.invalidateQueries('todos'); },});// 调用方式mutation.mutate(newTodo);核心区别操作类型:useQuery 用于读取操作,useMutation 用于写入操作缓存行为:useQuery 自动缓存数据,useMutation 不缓存结果调用方式:useQuery 自动执行(可配置),useMutation 需要手动调用 mutate 方法返回值:useQuery 返回数据和状态,useMutation 返回 mutation 函数和状态正确使用这两个钩子可以有效地管理应用中的数据获取和修改操作,提高开发效率和用户体验。
阅读 0·3月7日 12:25

React Query 与传统状态管理库(如 Redux)的区别是什么,什么时候应该使用 React Query?

React Query 和传统状态管理库(如 Redux)在设计理念和使用场景上有显著区别:核心区别管理的状态类型:React Query:专门管理服务器状态(从API获取的数据)Redux:管理全局客户端状态(如用户偏好、UI状态、应用配置等)状态管理方式:React Query:声明式数据获取,自动处理缓存、失效、重试等Redux:需要手动编写action、reducer来管理状态更新缓存机制:React Query:内置强大的缓存系统,自动处理数据的缓存和失效Redux:没有内置缓存机制,需要手动实现或使用额外库数据获取:React Query:集成了数据获取逻辑,支持自动重试、轮询等Redux:需要手动集成axios/fetch等库,处理异步逻辑代码复杂度:React Query:减少了样板代码,API简洁直观Redux:需要编写更多样板代码(action types、actions、reducers)适用场景何时使用 React Query:需要频繁与API交互的应用:React Query 专门优化了服务器状态管理需要缓存数据的场景:内置缓存机制减少重复请求需要乐观更新的场景:提升用户体验需要处理分页、无限滚动的场景:内置支持需要自动重试和错误处理的场景:简化错误处理逻辑何时使用传统状态管理库:需要管理复杂的客户端状态:如多级嵌套的UI状态需要中间件支持:如日志、路由、持久化等需要时间旅行调试:Redux DevTools提供强大的调试能力需要严格的状态变更控制:如金融应用最佳实践实际上,React Query 和传统状态管理库并不是互斥的,它们可以结合使用:使用 React Query 管理所有服务器状态使用 Redux/Zustand 等管理客户端状态这样可以充分发挥各自的优势,构建更高效、可维护的应用React Query 的出现并不是要完全取代传统状态管理库,而是为了解决服务器状态管理这一特定领域的问题,让开发人员能够更专注于业务逻辑和UI开发。
阅读 0·3月7日 12:25

如何在 React Query 中处理错误和重试,有哪些最佳实践?

React Query 提供了强大的错误处理和重试机制,帮助开发者构建更健壮的应用:错误处理基本错误处理: const { data, error, isError } = useQuery('todos', fetchTodos); if (isError) { return <div>Error: {error.message}</div>; }全局错误处理:通过 QueryClient 配置 const queryClient = new QueryClient({ defaultOptions: { queries: { onError: (error) => { console.error('Query error:', error); // 可以在这里添加全局错误通知 }, }, }, });Mutation 错误处理: const mutation = useMutation(addTodo, { onError: (error, variables, context) => { console.error('Mutation error:', error); // 错误处理逻辑 }, });重试机制基本重试配置: const { data } = useQuery('todos', fetchTodos, { retry: 3, // 默认值为 3 });高级重试配置: const { data } = useQuery('todos', fetchTodos, { retry: (failureCount, error) => { // 自定义重试逻辑 if (error.status === 404) return false; // 404 不重试 if (failureCount >= 3) return false; // 最多重试 3 次 return true; }, retryDelay: (attemptIndex) => { // 指数退避策略 return Math.min(1000 * 2 ** attemptIndex, 30000); }, });最佳实践错误边界:使用 React 错误边界捕获查询错误 <ErrorBoundary fallback={<ErrorComponent />}> <QueryComponent /> </ErrorBoundary>错误状态 UI:为不同类型的错误提供不同的 UI 反馈 if (isError) { if (error.status === 401) { return <div>请先登录</div>; } else if (error.status === 404) { return <div>资源不存在</div>; } else { return <div>发生错误:{error.message}</div>; } }重试策略:对网络错误使用重试对 5xx 服务器错误使用重试对 4xx 客户端错误(如 401、404)不使用重试错误日志:集成错误监控服务(如 Sentry) const queryClient = new QueryClient({ defaultOptions: { queries: { onError: (error) => { Sentry.captureException(error); }, }, }, });用户反馈:显示加载状态显示错误信息提供重试按钮乐观更新错误处理:在 mutation 中正确处理回滚 const mutation = useMutation(updateTodo, { onMutate: () => { /* 乐观更新 */ }, onError: (error, variables, context) => { // 回滚数据 queryClient.setQueryData('todos', context.previousTodos); // 显示错误信息 showNotification('更新失败', 'error'); }, });通过合理配置错误处理和重试机制,可以显著提高应用的可靠性和用户体验。
阅读 0·3月7日 12:25

React Query 有哪些高级特性,如依赖查询、并行查询和窗口聚焦重新获取?

React Query 提供了许多高级特性,使数据管理更加灵活和强大:1. 依赖查询(Dependent Queries)依赖查询是指一个查询的执行依赖于另一个查询的结果。使用场景:当你需要先获取一个资源的 ID,然后用这个 ID 获取详细信息时。实现方式:const { data: user } = useQuery(['user', userId], fetchUser);// 只有当 user 存在时才执行第二个查询const { data: userPosts } = useQuery( ['posts', user?.id], () => fetchPosts(user.id), { enabled: !!user });2. 并行查询(Parallel Queries)并行查询是指同时执行多个独立的查询。使用场景:当你需要在一个组件中获取多个不相关的数据时。实现方式:// 方式1:多个 useQuery 钩子const { data: users } = useQuery('users', fetchUsers);const { data: posts } = useQuery('posts', fetchPosts);// 方式2:使用 useQueries 钩子(React Query v3+)const results = useQueries([ { queryKey: ['users'], queryFn: fetchUsers }, { queryKey: ['posts'], queryFn: fetchPosts },]);3. 窗口聚焦重新获取(Window Focus Refetching)当用户重新聚焦浏览器窗口时,React Query 可以自动重新获取数据。使用场景:确保用户看到的是最新数据,特别是在多标签页应用中。配置方式:// 全局配置const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: true, // 默认值 }, },});// 单个查询配置const { data } = useQuery('todos', fetchTodos, { refetchOnWindowFocus: false, // 禁用});4. 分页和无限滚动(Pagination & Infinite Scroll)React Query 提供了专门的钩子来处理分页和无限滚动。实现方式:// 分页查询const { data, fetchNextPage, hasNextPage } = useInfiniteQuery( ['posts', pageSize], ({ pageParam = 1 }) => fetchPosts(pageParam, pageSize), { getNextPageParam: (lastPage, pages) => { return lastPage.hasMore ? pages.length + 1 : undefined; }, });5. 轮询(Polling)自动定期重新获取数据。使用场景:需要显示实时数据的应用,如仪表盘、聊天应用等。配置方式:const { data } = useQuery('todos', fetchTodos, { refetchInterval: 5000, // 每5秒重新获取 refetchIntervalInBackground: true, // 后台也轮询});6. 预取(Prefetching)提前获取可能需要的数据,提升用户体验。实现方式:// 手动预取queryClient.prefetchQuery('todos', fetchTodos);// 视口预取(使用 react-query/prefetch)usePrefetchQuery('todos', fetchTodos);7. 持久化缓存(Persisted Queries)将缓存数据持久化到 localStorage 或其他存储中。实现方式:使用 persistQueryClient 插件这些高级特性使得 React Query 能够应对各种复杂的数据获取场景,提供更灵活、更高效的数据管理方案。
阅读 0·3月7日 12:25

React Query 如何与 React Suspense 集成使用,有哪些注意事项?

React Query 支持与 React Suspense 集成,使得数据获取可以像处理组件渲染一样自然:基本集成方法启用 Suspense 模式: const { data } = useQuery('todos', fetchTodos, { suspense: true, });使用 Suspense 组件包裹: function TodoList() { const { data: todos } = useQuery('todos', fetchTodos, { suspense: true, }); return ( <ul> {todos.map(todo => ( <li key={todo.id}>{todo.title}</li> ))} </ul> ); } function App() { return ( <Suspense fallback={<div>Loading...</div>}> <TodoList /> </Suspense> ); }错误边界处理当使用 Suspense 模式时,错误需要通过 Error Boundary 捕获:class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false, error: null }; } static getDerivedStateFromError(error) { return { hasError: true, error }; } render() { if (this.state.hasError) { return <div>Error: {this.state.error.message}</div>; } return this.props.children; }}function App() { return ( <ErrorBoundary> <Suspense fallback={<div>Loading...</div>}> <TodoList /> </Suspense> </ErrorBoundary> );}注意事项兼容性:Suspense 模式需要 React 16.6+ 版本确保所有相关组件都支持 Suspense错误处理:必须使用 Error Boundary 捕获错误不能再使用 useQuery 返回的 error 和 isError 属性缓存行为:即使数据在缓存中,首次渲染时仍会触发 Suspense后续渲染会直接使用缓存数据,不会触发 Suspense性能考虑:Suspense 模式可能会导致组件树的多次渲染对于复杂应用,需要权衡用户体验和性能与其他功能的兼容性:窗口聚焦重新获取:可能会导致意外的 Suspense 触发轮询:需要谨慎配置,避免频繁触发 Suspense服务器端渲染:在 SSR 环境中使用 Suspense 需要特殊处理推荐使用 dehydrate 和 hydrate 方法最佳实践渐进式采用:先在非关键组件中尝试 Suspense 模式逐步扩展到整个应用合理使用 fallback:提供有意义的加载状态避免使用过于简单的加载指示器结合预取:使用 queryClient.prefetchQuery 提前获取数据减少 Suspense 的触发次数错误边界策略:在适当的层级放置 Error Boundary提供具体的错误信息和恢复选项通过合理集成 React Query 和 Suspense,可以创建更加流畅、响应迅速的用户界面,同时保持代码的简洁性和可读性。
阅读 0·3月7日 12:25