Ethan 发布的文章 - CorePlayers
首页
我的项目
708 智能控制台
Ethan认证中心
Ethan's ToDoList
Ethan's Tech Blog
好友链接
妙站分享
联系站长
搜 索
1
2026技术架构新趋势:从微服务回调到AI原生架构设计
45 阅读
2
DDD领域驱动设计:从底层原理到生产级全链路落地实战
41 阅读
3
Go + 云原生2026:从微服务到AI Infra的实战架构
35 阅读
4
事件驱动架构(EDA):从理论到项目落地的完整实践
34 阅读
5
2026 AI编程范式演进:从Vibe Coding到Spec-Driven Development再到Harness Engineering
31 阅读
ALL
(78)
AI
(20)
前端
(24)
后端
(23)
Dify/Coze
(7)
架构设计
(6)
登录
/
注册
搜 索
标签搜索
AI Agent
边缘计算
RSC
虚拟线程
Java
Spring Boot 4
Vibe Coding
AI原生
SDD
全栈开发
高并发
Project Loom
性能优化
2026趋势
协议标准
工具调用
MCP协议
多Agent协作
CrewAI
Spring AI
EthanCcc
累计撰写
78
篇文章
累计收到
1
条评论
首页
栏目
ALL
AI
前端
后端
Dify/Coze
架构设计
页面
我的项目
708 智能控制台
Ethan认证中心
Ethan's ToDoList
Ethan's Tech Blog
好友链接
妙站分享
联系站长
用户登录
登录
注册
作者:Ethan
2025-06-22
前后端交互全解析:Axios 封装、跨域方案与 Token 认证机制
前后端交互是现代 Web 应用中最核心的环节之一,涉及请求封装、跨域处理、认证鉴权和数据缓存等多个方面。一个设计良好的 HTTP 交互层可以显著提升开发效率和代码可维护性。本文将系统梳理前后端交互的关键技术点。Axios 企业级封装直接使用 axios 发起请求会导致大量重复代码。一个规范的封装应该覆盖 Token 注入、请求签名、统一错误处理、请求重试和 Token 刷新等能力。// utils/request.ts\nimport axios from 'axios';\nconst service = axios.create();\n\n// 请求拦截器\nservice.interceptors.request.use((config) => `;\n }\n // 时间戳防缓存(GET 请求)\n if (config.method === 'get') ;\n }\n return config;\n});\n\n// 响应拦截器\nservice.interceptors.response.use(\n (response) => = response.data;\n if (code === 200) return data;\n if (code === 401) \n return Promise.reject(new Error(message));\n },\n (error) => \n);跨域问题:从原理到解决方案为什么会有跨域?浏览器的同源策略(Same-Origin Policy)要求协议、域名、端口三者完全一致才允许访问。这是浏览器的安全机制,防止恶意网站窃取数据。需要注意的是,跨域限制是浏览器的行为,服务端之间的请求不受限制。方案一:CORS(后端配置,最主流)// SpringBoot 配置\n@Configuration\npublic class CorsConfig implements WebMvcConfigurer \n}方案二:开发环境代理(Vite 配置,最常用)// vite.config.ts\nserver: \n }\n}方案三:Nginx 反向代理(生产环境首选)前端和后端部署在同一域名下,通过 Nginx 将 /api/ 路径代理到后端服务,彻底避免跨域问题。Token 认证机制深度对比JWT(JSON Web Token)JWT 由三部分组成(Header.Payload.Signature),每部分用 Base64 编码。后端签发后,前端存储在 localStorage 或 cookie 中,每次请求携带在 Authorization 头中。JWT 的核心争议:无状态 vs 不可撤销。JWT 签发后服务端无法主动使其失效——如果你的 access_token 有效期是 2 小时,那么即使你在第 1 分钟就退出登录,攻击者拿到这个 token 后仍可在剩余的 1 小时 59 分内使用它。解决方案:短 access_token + 长 refresh_token(access_token 有效期 15-30 分钟,refresh_token 有效期 7-30 天)或 Token 黑名单(在 Redis 中维护已注销 token 的黑名单)。无感刷新 Token 机制(关键)let isRefreshing = false;\nlet refreshSubscribers = [];\n\nservice.interceptors.response.use(\n (response) => response,\n async (error) => = error;\n if (response?.status !== 401 || config._retry) return Promise.reject(error);\n if (!isRefreshing) = await refreshToken();\n isRefreshing = false;\n refreshSubscribers.forEach(cb => cb(token));\n refreshSubscribers = [];\n return service(config); // 用新 token 重试\n }\n return new Promise((resolve) => `;\n resolve(service(config));\n });\n });\n }\n);前端缓存策略缓存类型存储位置容量过期策略适用场景localStorage浏览器5MB手动清除用户偏好、主题设置sessionStorage浏览器5MB关闭标签页时清除表单草稿、临时状态Cookie浏览器/服务端4KB可设过期时间Token 存储、会话标识IndexedDB浏览器无上限手动管理大量结构化数据、离线应用HTTP 缓存浏览器/代理由响应头控制Cache-Control 头静态资源缓存总结前后端交互的核心设计原则:请求层统一封装,不要散落在各处;认证机制无感刷新,用户登录一次便不再被打断;错误处理统一收敛,不要让每个调用方都写 try-catch;缓存策略分层设计,该缓存的绝不重复请求。
2025年06月22日
11
0
1
2025-06-20
Next.js 15 App Router 最佳实践:路由、缓存与数据获取
Next.js 15 将 Server Components 和 Server Actions 纳入稳定功能,Turbopack Dev 也达到生产可用状态,并重构了缓存策略。本文梳理 2025 年 Next.js 15 App Router 的核心最佳实践。一、Server Components 优先策略app/ 目录下的组件默认都是服务端组件,基本原则是:尽量让父组件(页面)是服务端组件负责取数据,把数据作为 props 传给客户端组件负责交互。// ✅ 推荐:Server Component 直接获取数据 export default async function Page() { const data = await fetch('https://api.example.com/data'); const json = await data.json(); return <div></div>; } // ❌ 避免:不必要的 Client Component 'use client'; export default function Page() { const [data, setData] = useState(null); useEffect(() => { fetch('/api/data').then(r => r.json()).then(setData); }, []); return <div></div>; }二、数据获取策略Next.js 对原生 fetch 做了扩展,支持缓存控制:// ISR 模式:每 24 小时重新验证 fetch(url, }); // SSR 模式:每次请求都重新获取 fetch(url, ); // SSG 模式:永久缓存 fetch(url, );在 Server Component 中推荐使用原生 fetch 而非 axios,因为只有原生 fetch 能享受 Next.js 的缓存扩展能力。axios 在客户端组件中完全可用。三、路由系统要点文件即路由: app/page.tsx → / app/about/page.tsx → /about app/posts/[id]/page.tsx → /posts/:id(动态路由) app/blog/[...slug]/page.tsx → /blog/*(捕获所有路由) Next.js 15 重要变更:params 现在是 Promise 类型,需要使用 await:export default async function PostPage(: > }) { const = await params; // ... }四、缓存策略重构Next.js 15 将缓存从"隐式默认"改为"显式可控": GET 路由处理程序默认不再缓存 客户端路由缓存默认不再缓存页面组件 开发者需要显式声明缓存策略 这一变更使行为更加可预测,但也要求开发者明确思考每个请求的缓存需求。五、路由守卫实现在项目根目录创建 middleware.ts:import from 'next/server'; import type from 'next/server'; export function middleware(request: NextRequest) { const token = request.cookies.get('token')?.value; if (!token) { return NextResponse.redirect(new URL('/login', request.url)); } }六、并行数据获取利用 Promise.all 避免瀑布式请求:const [user, posts, comments] = await Promise.all([ getUser(params.id), getPosts(params.id), getComments(params.id), ]);
2025年06月20日
11
0
1
2025-06-10
Redis 7.4 新数据结构与高并发缓存策略深度优化
Redis 7.4 作为 2025 年的主力版本,带来了多项对企业级缓存架构有深远影响的新特性。本文将从新数据结构入手,探讨在高并发场景下的缓存策略优化方案。Redis 7.4 新数据结构1. JSON 类型正式版:RedisJSON 从模块进化为内置数据类型。支持 JSONPath 查询和原子级部分更新,性能比存储序列化的 JSON 字符串提升 3-8 倍,尤其在只修改 JSON 对象的一个字段时优势巨大。JSON.SET user:1001 $.name "张三" JSON.GET user:1001 $.address.city JSON.ARRAPPEND user:1001 $.tags '\"redis\"'2. 改进的 Stream 消费者组:支持消费者组间的消息广播模式,一个消息可以被多个组独立消费,适合多系统联动场景。3. 向量搜索(预览):内置的向量相似度搜索支持 ITEM、L2、COSINE 三种距离度量,为 AI 应用的轻量级语义缓存提供了新的可能。高并发缓存策略优化缓存穿透:使用 Redis 7.4 的 SET 命令新增的 NX 和 EXAT 组合,结合布隆过滤器实现零穿透缓存。对于不存在的数据,缓存空值 60 秒,防止数据库被击穿。缓存雪崩:采用随机过期时间(TTL ± 20% 随机偏移),避免大量 key 同时过期。Redis 7.4 的 EXPIRE 命令支持批量设置和随机偏移,一改之前只能逐个设置的限制。热点缓存:对于那些 QPS 极高的热点 key,使用 Redis Cluster 的哈希标签(Hash Tag)确保热点数据分散在多个节点上,配合客户端本地缓存(如 Caffeine)形成两级缓存架构。
2025年06月10日
11
0
1
2025-06-05
Redis + Caffeine 多级缓存架构与分布式锁实战
缓存是高并发系统的基石。单靠 Redis 已经不足以应对极致性能需求——本地缓存 + 分布式缓存的多级架构才是最优解。本文深入 Redis 核心机制、Caffeine 本地缓存、多级缓存设计和分布式锁实战。Redis 核心数据结构与使用场景 数据结构底层实现典型场景 StringSDS缓存对象、计数器、分布式锁 Hashziplist / hashtable存储对象属性 Listquicklist消息队列、最新列表 Setinset / hashtable标签、共同好友 ZSetziplist / skiplist排行榜、延迟队列 Streamradix tree + listpack消息队列(支持消费组) 缓存穿透、击穿、雪崩:三板斧1. 缓存穿透:查不存在的数据// 布隆过滤器 BF.RESERVE userFilter 0.01 1000000 BF.ADD userFilter "user:999999" // 查询前先判断布隆过滤器:不存在则直接拒绝 // 缓存空值 String value = redis.get(key); if (value == null) { value = db.query(key); if (value == null) { redis.setex(key, 60, "NULL"); // 缓存空值 60 秒 return null; } redis.set(key, value, 300); }2. 缓存击穿:热点 Key 过期public String getWithMutex(String key) { String value = redis.get(key); if (value != null) return value; String lockKey = "lock:" + key; if (redis.setnx(lockKey, "1")) { redis.expire(lockKey, 10); try { value = db.query(key); redis.set(key, value, 300); } finally { redis.del(lockKey); } return value; } else { Thread.sleep(50); return getWithMutex(key); // 递归重试 } }3. 缓存雪崩:大量 Key 同时过期// TTL 加随机偏移 int ttl = 300 + ThreadLocalRandom.current().nextInt(60); // 300~360秒 redis.setex(key, ttl, value); // 多级缓存兜底 public Object get(String key) { Object val = caffeine.get(key); // L1: 本地缓存 if (val != null) return val; val = redis.get(key); // L2: Redis if (val != null) { caffeine.put(key, val); // 回填 L1 return val; } val = db.query(key); // L3: 数据库 redis.set(key, val, ttl + random(60)); // 回填 L2 caffeine.put(key, val); // 回填 L1 return val; }Caffeine 本地缓存配置Cache<String, Object> cache = Caffeine.newBuilder() .maximumSize(10000) // 最大条目数 .expireAfterWrite(5, TimeUnit.MINUTES) .expireAfterAccess(3, TimeUnit.MINUTES) .recordStats() // 开启统计(命中率监控) .build();Redis 分布式锁:Redisson 实战@Autowired private RedissonClient redisson; public void processOrder(String orderId) { RLock lock = redisson.getLock("order:" + orderId); try { // 尝试加锁,最多等待 10 秒,锁 30 秒后自动释放 if (lock.tryLock(10, 30, TimeUnit.SECONDS)) { // 业务逻辑 orderService.process(orderId); } } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } // Redisson 的 WatchDog 机制:自动续期 // 如果业务还没执行完,WatchDog 每隔 10 秒自动续期到 30 秒Redis 哨兵集群搭建# sentinel.conf sentinel monitor mymaster 192.168.1.10 6379 2 # 2 个哨兵同意才判定下线 sentinel down-after-milliseconds mymaster 5000 # 5 秒无响应判定主观下线 sentinel failover-timeout mymaster 60000 # 故障转移超时 60 秒 sentinel parallel-syncs mymaster 1 # 一次只同步一台新主库总结多级缓存架构的核心思想是"近处快,远处稳"。Caffeine 提供微秒级响应但空间有限,Redis 提供毫秒级响应且可共享,数据库是最后的兜底。分布式锁的关键在于原子性(SETNX)、防死锁(过期时间)、防误删(Lua 脚本校验值)和可重入(Redisson WatchDog)。
2025年06月05日
10
0
1
2025-06-05
浏览器事件循环与页面渲染机制完全指南
事件循环(Event Loop)是 JavaScript 运行时的核心机制,也是面试和实际开发中都无法绕开的关键知识点。不理解 Event Loop,你就无法解释 setTimeout 的延迟为什么不准、为什么某些动画会卡顿、以及为什么 async/await 的行为有时会出乎意料。本文将系统性地解析 Event Loop 和页面渲染的关系。JavaScript 的单线程本质JavaScript 被设计为单线程语言,主要原因是作为浏览器脚本语言,它需要操作 DOM——如果有两个线程同时操作同一个 DOM 节点,结果将不可预测。但单线程带来了一个问题:如果一个任务执行时间过长,页面就会"卡住"。Event Loop 正是为了解决这个问题而设计的。宏任务与微任务:事件循环的两层结构事件循环中,任务分为两种优先级:宏任务(MacroTask)——包括 script 整体、setTimeout、setInterval、I/O、UI 渲染、postMessage 等,每次事件循环取一个执行。微任务(MicroTask)——包括 Promise.then/catch/finally、MutationObserver、queueMicrotask、process.nextTick 等,宏任务执行完后一次性清空所有微任务。事件循环的完整流程浏览器的事件循环可以概括为以下循环过程:(1) 从宏任务队列中取出一个宏任务执行;(2) 执行过程中产生的微任务放入微任务队列;(3) 当前宏任务执行完毕后,清空微任务队列(包括微任务中新产生的微任务);(4) 如果需要,执行 UI 渲染(requestAnimationFrame 回调在此阶段);(5) 回到步骤 1,进入下一轮事件循环。console.log('1');\nsetTimeout(() => , 0);\nPromise.resolve().then(() => );\nconsole.log('6');\n// 输出:1 → 6 → 4 → 2 → 3 → 5async/await 的本质:Promise 的语法糖async function foo() \n// 等价于:\nfunction foo() );\n}Event Loop 与页面渲染的关系浏览器大约每 16.6ms(60fps)进行一次页面渲染。渲染发生在一轮事件循环的宏任务和微任务都执行完毕后。这意味着:微任务太多会阻塞渲染(如果微任务不断产生新的微任务,渲染将被无限推迟);requestAnimationFrame 在渲染前执行,适合做动画相关的计算;requestIdleCallback 在渲染后、下一轮循环前的空闲时间执行,适合做低优先级工作。// 危险:死循环微任务会永久阻塞渲染\nfunction blockingMicroTask() );\n}\n// requestIdleCallback:在浏览器空闲时执行非关键任务\nrequestIdleCallback((deadline) => \n});重排(Reflow)与重绘(Repaint)重排(Reflow):元素的几何属性(位置、尺寸)发生变化 → 浏览器重新计算布局。触发操作包括修改 width/height、添加/删除 DOM、改变字体大小、读取 offsetHeight 等布局属性。重绘(Repaint):元素的视觉属性(颜色、背景)发生变化但不影响布局 → 只重新绘制。重排一定触发重绘,但重绘不一定触发重排。重排的成本远高于重绘。// 错误:强制同步布局,每次读取 offsetHeight 都触发重排\nfor (let i = 0; i < 1000; i++) \n// 正确:批量读写分离\nconst width = element.offsetWidth; // 先读\nconst height = element.offsetHeight;\nelement.style.width = width + 10 + 'px'; // 后写\nelement.style.height = height + 10 + 'px';总结理解事件循环和渲染机制后,你在写代码时会自然地做出更好的决策:长列表用虚拟滚动、动画用 requestAnimationFrame、非关键任务用 requestIdleCallback、合并 DOM 操作避免强制同步布局。这些看似微小的优化,积累起来就是用户体验的巨大差异。
2025年06月05日
14
0
1
1
...
10
11
12
...
16