news 2026/9/19 17:04:49

eShopOnWeb性能优化指南:用装饰器模式为商品查询加上1分钟内存缓存

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
eShopOnWeb性能优化指南:用装饰器模式为商品查询加上1分钟内存缓存

eShopOnWeb性能优化指南:用装饰器模式为商品查询加上1分钟内存缓存

【免费下载链接】eShopOnWebSample ASP.NET Core 8.0 reference application, now community supported: https://github.com/NimblePros/eShopOnWeb项目地址: https://gitcode.com/gh_mirrors/es/eShopOnWeb

eShopOnWeb 是 Microsoft 官方的 ASP.NET Core 8.0 参考应用(社区版),它演示了一个完整的电商系统:Web 商城、API 服务和 Blazor 后台管理。对于新手来说,这个项目里最值得学习的性能优化点之一,就是如何用装饰器模式(Decorator Pattern)为商品查询添加内存缓存——在不改动原有业务代码的前提下,让重复的商品列表查询在 1 分钟内直接命中缓存,不再反复调用 API。

项目背景:为什么商品查询需要缓存?

eShopOnWeb 的商品数据流是这样的:

  • 用户浏览商城首页 → Web 应用查询商品列表、品牌、类型
  • 每次查询都要访问数据库或 API,产生网络开销和数据库压力
  • 商品、品牌、类型这类数据变化频率低、读取频率高,是典型的缓存好场景

项目的解决思路很克制:不修改原始服务类,而是"包一层"带缓存逻辑的新类,通过依赖注入把新类交给页面使用。这就是装饰器模式的经典用法。

一图看懂装饰器模式

装饰器模式的核心思想:给已有对象动态叠加新功能,而不改动它本身

请求 → [带缓存的服务] → 命中缓存?── 是 → 直接返回 │ 否 ↓ [原始 HTTP 服务] → 调用 API → 结果写入缓存 → 返回

它的优势:

  • ✅ 原始服务代码零改动,职责保持单一
  • ✅ 缓存逻辑可以独立测试、独立替换
  • ✅ 通过依赖注入(DI)注册,切换有无缓存只需改一行注册代码

项目中的 3 种缓存方案对比

eShopOnWeb 在不同模块用了三种不同的缓存手段,放在一起对比,能帮你理解各自的适用场景:

方案所在模块缓存位置过期策略缓存键
装饰器 + 内存缓存Web 商城(CachedCatalogViewModelService服务器内存(IMemoryCache)30 秒滑动过期brandstypesitems-{页}-{每页}-{品牌}-{类型}
装饰器 + 本地存储BlazorAdmin 后台(CachedCatalogItemServiceDecorator浏览器 LocalStorage1 分钟绝对过期固定键items
泛型装饰器 + 本地存储BlazorAdmin 后台(品牌/类型下拉数据)浏览器 LocalStorage1 分钟绝对过期数据类名(如CatalogBrand

可以看到,同一个"商品查询",在 Web 端缓存服务器内存,在 Blazor 后台则缓存在浏览器端——因为 Blazor 页面是前端直接调 API 的,把数据留在浏览器里能省掉整个网络往返。

Web 端:IMemoryCache 装饰器的关键设计

Web 端的商品查询装饰器位于 CachedCatalogViewModelService.cs,它包裹了原始的 CatalogViewModelService.cs,对品牌、类型、分页商品列表三类查询统一加缓存。

其中最有学习价值的是缓存键的设计,实现在 CacheHelpers.cs:

缓存对象键生成规则说明
商品列表items-{pageIndex}-{pageSize}-{brandId}-{typeId}每个"页 + 筛选条件"组合都有独立缓存
品牌列表brands固定键
类型列表types固定键

为什么商品列表的键要包含页码和筛选条件?因为用户筛选"品牌 = Nike + 第 2 页"和"未筛选 + 第 1 页"看到的商品完全不同,如果共用一个键,就会把错误的商品数据返回给页面。默认过期时长为30 秒滑动过期DefaultCacheDuration)——只要持续有人访问,缓存就一直续命;没人访问 30 秒后自动清除。

BlazorAdmin 端:装饰器 + 浏览器本地存储

后台管理端的装饰器位于 CachedCatalogItemServiceDecorator.cs,思路和 Web 端一致,但缓存介质换成了浏览器的 LocalStorage。

它的请求流程分三步:

  1. 读缓存:以固定键items从本地存储取出缓存条目
  2. 判过期:缓存条目创建时间 + 1 分钟 > 当前时间?命中则直接返回;否则删除旧缓存
  3. 回源:委托给原始的 CatalogItemService.cs 调 API,拿到结果后连同创建时间一起写回本地存储

这里有一个精巧的小类 CacheEntry.cs:它把"缓存数据 + 创建时间"打包成一个整体,让过期判断有了依据。

写操作后的缓存刷新(最容易遗漏的细节)

只缓存读取、不处理写入,会导致页面展示"陈旧数据"。装饰器对Create/Edit/Delete三个写方法的处理方式是:

  1. 先委托原始服务完成真实写操作
  2. 再调用RefreshLocalStorageList():删除旧缓存 → 重新拉取最新列表 → 写入新缓存

这样"新增商品后刷新页面"这类操作永远不会看到过期数据,而普通浏览操作则享受缓存加速。

品牌、类型这两个下拉数据源用的是一个泛型装饰器CachedCatalogLookupDataServiceDecorator.cs,用类型名自动做缓存键,一份代码同时服务两种数据。

依赖注入注册:整个方案的"开关"

两个装饰器最终在 ServicesConfiguration.cs 中完成注册,规则很简单:

  • 接口ICatalogItemService→ 绑定到装饰器类(页面拿到的就是带缓存的版本)
  • 具体类CatalogItemService→ 按自身类型单独注册(供装饰器内部构造注入使用)

也就是说,装饰器通过构造函数持有原始服务的实例,接口调用时走装饰器,装饰器内部再决定是否真正调原始服务。想临时关掉缓存?把接口的绑定换回具体类即可,业务代码一行都不用动。

关键源码导航

文件作用
src/Web/Services/CachedCatalogViewModelService.csWeb 端商品查询的内存缓存装饰器
src/Web/Extensions/CacheHelpers.cs缓存键生成规则与 30 秒过期配置
src/BlazorAdmin/Services/CachedCatalogItemServiceDecorator.csBlazor 后台商品列表的本地存储缓存装饰器
src/BlazorAdmin/Services/CacheEntry.cs缓存条目包装类(数据 + 创建时间)
src/BlazorAdmin/ServicesConfiguration.cs装饰器与原始服务的 DI 注册开关

常见问题(FAQ)

Q1:为什么叫"装饰器模式"而不是直接改原服务加缓存?保持单一职责。原始服务只负责"取数据",装饰器只负责"缓存"。将来想换 Redis、想加分布式锁,都只需新增一个装饰器类,不动老代码。

Q2:1 分钟 / 30 秒的过期时间是怎么定的?两者取向不同:Web 端用滑动过期(有人访问就续命),适合高热度查询;Blazor 端用绝对过期(创建后 1 分钟必失效),保证后台编辑数据后很快能看到最新状态。

Q3:写操作为什么必须刷新缓存?缓存放的是快照。如果新增商品后不清缓存,列表页会继续展示旧数据,用户会以为"操作没生效"。装饰器里CreateEditDelete后统一调用刷新方法,正是为了消除这个体验断层。

小结

eShopOnWeb 用一套组合拳演示了装饰器模式在性能优化中的标准姿势:

  • 装饰器包原始服务,接口不变、代码不改
  • 读操作走缓存 + 短过期,写操作强制刷新
  • 缓存键包含全部筛选条件,避免脏数据串页
  • DI 注册处即"缓存开关"

把这套模式迁移到你自己的项目里,商品类查询的重复请求量可以大幅减少,而实现成本不过百来行代码。

【免费下载链接】eShopOnWebSample ASP.NET Core 8.0 reference application, now community supported: https://github.com/NimblePros/eShopOnWeb项目地址: https://gitcode.com/gh_mirrors/es/eShopOnWeb

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 17:04:42

2023数据中台建设方案:DataOps驱动的MVP落地实践

简介:本资源为一份面向企业数字化转型实践者、数据架构师与中台建设团队的2023年数据中台项目建设方案,聚焦解决多源数据分散、治理低效、指标口径不一、资产价值难量化等典型痛点。方案全文以Word文档(.docx)形式呈现&#xff0c…

作者头像 李华
网站建设 2026/9/19 17:02:19

DeepSeek 降重版测评,论文改写的 Base URL 填 TaoToken 的 API 地址

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 17:00:20

社区健康志愿服务APP开发:FastAPI订单状态机与GPS签到实践

简介:“乐助”社区健康志愿服务APP平台的设计与构建是一篇聚焦社区志愿管理数字化的学术论文,面向健康服务、护理信息化及APP开发方向的从业者和研究者。论文以沈阳市5个社区180名居民为调查对象,采用自研问卷得出居民参与意愿达80.9%、对志愿…

作者头像 李华
网站建设 2026/9/19 16:59:57

STM32 ADC片内信号采集实战:从温度传感器到VREFINT的完整链路

1. 为什么我要用片内信号做ADC演示而不是外接传感器很多人第一次接触STM32的ADC,第一反应是找个电位器或者光敏电阻接上去,拧一拧、遮一遮,看串口输出的数字跳来跳去,觉得“跑通了”。但真到了项目里,比如做数字电源的…

作者头像 李华
网站建设 2026/9/19 16:59:47

MindSpore Transformers训练在线监控:实时曲线可视化方案从0到1

在深度学习训练里,我吃过最大的亏就是“闷头跑实验”。模型扔上去,loss 一开始降得挺漂亮,我就放心去忙别的了,等第二天回来看日志,发现 loss 早就发散成一条冲天直线,而且是在好几个小时之前就爆掉了——那…

作者头像 李华
网站建设 2026/9/19 16:57:00

STM32+ESP8266+OneNet工业物联网实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华