news 2026/11/4 7:41:25

博客每日一句功能实现:双层缓存与响应式设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
博客每日一句功能实现:双层缓存与响应式设计

1. 项目概述

在个人博客中添加"每日一句"功能是个提升用户体验的好方法。这个功能每天展示不同的中英文双语名言警句,既能增加网站的内容丰富度,又能给访客带来一些思考和启发。我最近在自己的博客上实现了这个功能,经过几周的运行测试,效果相当不错。

这个功能的实现涉及前后端多个技术点,包括API代理、数据缓存、响应式设计和交互优化等。最核心的是采用了双层缓存架构:服务端使用ConcurrentHashMap缓存API返回的数据,客户端使用localStorage存储当天的句子,这样能最大程度减少对第三方API的调用,提高响应速度。

2. 功能需求与技术选型

2.1 核心功能需求

在设计这个功能时,我确定了以下几个核心需求:

  1. 每日自动更新:句子内容需要每天自动更换,不需要人工干预
  2. 高可用性:即使第三方API不可用,也要有备用方案保证功能正常
  3. 性能优化:避免重复请求API,减少网络开销
  4. 响应式设计:在各种设备上都能良好显示
  5. 交互体验:长句子支持展开/收起,方便阅读

2.2 技术选型分析

基于上述需求,我选择了以下技术栈:

  • 后端:Spring Boot + RestTemplate

    • Spring Boot提供了快速开发REST API的能力
    • RestTemplate适合简单的HTTP请求场景
  • 前端:原生JavaScript + CSS3

    • 不依赖任何框架,减少资源加载
    • CSS3提供了丰富的样式和动画效果
  • 数据源:有道词典每日一句API

    • 提供高质量的中英文双语内容
    • 接口相对稳定
  • 缓存策略:

    • 服务端:ConcurrentHashMap
    • 客户端:localStorage

选择这些技术主要考虑以下几点:

  1. 轻量级,不会给博客增加太多负担
  2. 成熟稳定,减少维护成本
  3. 性能良好,响应速度快

3. 后端实现详解

3.1 API接口设计

后端API设计遵循RESTful风格,返回JSON格式数据:

@GetMapping(value = "/api/daily-quote", produces = "application/json;charset=UTF-8") @ResponseBody public Map<String, String> getDailyQuote() { // 实现逻辑... }

接口返回的数据结构包含三个字段:

  • en:英文句子
  • cn:中文翻译
  • success:是否成功获取数据

这样设计的好处是:

  1. 统一了API响应格式
  2. 前端可以方便地判断请求是否成功
  3. 支持后续扩展更多字段

3.2 缓存策略实现

3.2.1 ConcurrentHashMap缓存

服务端使用ConcurrentHashMap作为内存缓存:

private static final ConcurrentHashMap<String, Map<String, String>> quoteCache = new ConcurrentHashMap<>(); String today = java.time.LocalDate.now().toString(); if (quoteCache.containsKey(today)) { return quoteCache.get(today); }

选择ConcurrentHashMap的原因:

  1. 线程安全:支持多线程并发访问,适合Web应用场景
  2. 高性能:内存操作速度快,减少IO开销
  3. 简单易用:不需要额外依赖,Spring环境原生支持

缓存键使用当前日期字符串,这样每天会自动生成新的缓存条目,旧数据会被自动覆盖。

3.2.2 API请求实现

调用有道词典API的代码:

String apiUrl = "https://dict.youdao.com/infoline/style/cardList?mode=publish&client=mobile&style=daily"; HttpHeaders headers = new HttpHeaders(); headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); headers.add("Accept", "application/json, text/plain, */*"); headers.add("Host", "dict.youdao.com"); HttpEntity<String> requestEntity = new HttpEntity<>(headers); ResponseEntity<String> responseEntity = restTemplate.exchange( apiUrl, HttpMethod.GET, requestEntity, String.class );

这里有几个关键点:

  1. 请求头伪装:模拟浏览器请求,避免被识别为爬虫
  2. 异常处理:需要对各种异常情况进行捕获和处理
  3. 超时设置:建议添加合理的超时时间,避免请求卡死

3.3 数据解析与处理

API返回的是JSON数组,我们需要解析第一个元素:

JsonNode rootNode = objectMapper.readTree(responseStr); if (rootNode.isArray() && rootNode.size() > 0) { JsonNode firstItem = rootNode.get(0); String en = firstItem.get("title").asText().trim(); String cn = firstItem.get("summary").asText().trim(); result.put("en", en); result.put("cn", cn); result.put("success", "true"); quoteCache.put(today, result); }

解析时需要注意:

  1. 检查JSON结构是否符合预期
  2. 对文本内容进行trim处理,去除多余空格
  3. 处理可能的null值情况

3.4 降级策略实现

为了保证功能的高可用性,必须实现降级策略:

} catch (Exception e) { log.error("获取每日一句失败", e); } // 所有方案都失败时返回备用数据 result.put("en", "The best time to plant a tree was 10 years ago. The second best time is now."); result.put("cn", "种一棵树最好的时间是十年前,其次是现在。"); result.put("success", "false");

降级策略的设计要点:

  1. 捕获所有可能的异常
  2. 记录错误日志便于排查问题
  3. 提供有意义的默认内容
  4. 通过success字段告知前端这是降级数据

4. 前端实现详解

4.1 HTML结构设计

前端HTML结构保持简洁:

<div class="daily-quote" id="daily-quote"> <div class="daily-quote-content"> <div class="daily-quote-en" id="quote-en">Loading...</div> <div class="daily-quote-cn" id="quote-cn">正在获取每日一句...</div> </div> </div>

设计考虑:

  1. 使用语义化的class命名
  2. 提供有意义的加载状态提示
  3. 保持结构简单,便于样式控制

4.2 CSS样式实现

4.2.1 基础样式
.daily-quote { display: flex; flex-direction: column; justify-content: center; padding: 16px 20px; height: 80px; width: 360px; min-width: 360px; background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); border-radius: 16px; border: 1px solid #bae6fd; transition: all 0.3s ease; box-shadow: 0 2px 8px rgba(14, 165, 233, 0.06); position: relative; overflow: hidden; } /* 引号装饰 */ .daily-quote::before { content: '"'; position: absolute; top: 8px; left: 12px; font-size: 3rem; color: rgba(14, 165, 233, 0.15); font-family: Georgia, serif; line-height: 1; pointer-events: none; }

样式设计要点:

  1. 使用渐变色背景提升视觉效果
  2. 添加微妙的阴影和边框增强层次感
  3. 使用伪元素添加装饰性引号
  4. 设置合理的过渡动画
4.2.2 文字截断与展开
.daily-quote-en { font-size: 1.05rem; color: #0369a1; font-weight: 500; line-height: 1.4; font-style: italic; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; position: relative; transition: all 0.3s ease; } /* 悬浮/展开状态 */ .daily-quote-en:hover, .daily-quote-en.expanded { white-space: normal; overflow: visible; text-overflow: clip; background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%); padding: 4px 8px; border-radius: 6px; box-shadow: 0 4px 12px rgba(14, 165, 233, 0.15); z-index: 10; }

文字处理技巧:

  1. 使用text-overflow实现省略号效果
  2. 通过改变white-space属性实现展开/收起
  3. 添加悬停状态提升交互体验
  4. 使用z-index确保展开内容不被遮挡

4.3 JavaScript交互实现

4.3.1 数据获取与缓存
(function fetchDailyQuote() { var quoteEn = document.getElementById('quote-en'); var quoteCn = document.getElementById('quote-cn'); // 检查元素是否存在,不存在则重试 if (!quoteEn || !quoteCn) { setTimeout(fetchDailyQuote, 500); return; } // 检查本地缓存 var cachedDate = localStorage.getItem('dailyQuoteDate'); var today = new Date().toDateString(); if (cachedDate === today) { var cachedData = localStorage.getItem('dailyQuote'); if (cachedData) { try { var data = JSON.parse(cachedData); quoteEn.textContent = data.en; quoteCn.textContent = data.cn; return; } catch (e) { console.error('解析缓存数据失败', e); } } } // 调用后端接口 fetch('/api/daily-quote') .then(function(response) { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(function(data) { if (data && data.en && data.cn) { quoteEn.textContent = data.en; quoteCn.textContent = data.cn; // 缓存到 localStorage localStorage.setItem('dailyQuote', JSON.stringify({ en: data.en, cn: data.cn })); localStorage.setItem('dailyQuoteDate', today); } }) .catch(function(error) { console.error('Error:', error); // 使用备用数据 quoteEn.textContent = "The best time to plant a tree was 20 years ago..."; quoteCn.textContent = "种一棵树最好的时间是十年前,其次是现在。"; }); })();

缓存策略实现要点:

  1. 首先检查本地是否有当天的缓存
  2. 有缓存则直接使用,减少网络请求
  3. 无缓存或缓存过期才请求后端API
  4. 请求成功后将数据缓存到localStorage
  5. 所有环节都有错误处理和降级方案
4.3.2 点击展开功能
// 移动端点击展开/收起 function toggleExpand(element) { element.classList.toggle('expanded'); } quoteEn.addEventListener('click', function(e) { e.stopPropagation(); toggleExpand(quoteEn); }); quoteCn.addEventListener('click', function(e) { e.stopPropagation(); toggleExpand(quoteCn); }); // 点击其他地方收起 document.addEventListener('click', function() { quoteEn.classList.remove('expanded'); quoteCn.classList.remove('expanded'); });

交互设计考虑:

  1. 点击句子展开/收起完整内容
  2. 点击其他区域自动收起
  3. 使用stopPropagation防止事件冒泡
  4. 通过classList动态添加/移除类名

5. 双层缓存架构详解

5.1 缓存层次结构

┌─────────────────────────────────────────┐ │ 客户端 localStorage │ │ - 当天数据缓存 │ │ - 减少重复请求 │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ 服务端 ConcurrentHashMap │ │ - 当天数据缓存 │ │ - 减少第三方 API 调用 │ └─────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────┐ │ 第三方 API │ │ - 有道词典每日一句 │ └─────────────────────────────────────────┘

5.2 缓存策略对比

缓存层存储位置有效期作用
localStorage浏览器1天减少页面刷新后的请求
ConcurrentHashMap服务端内存1天减少第三方API调用

这种双层缓存架构的优势:

  1. 减少网络请求:大部分情况下只需要访问本地缓存
  2. 降低API压力:多个用户共享同一个服务端缓存
  3. 提升响应速度:内存操作远快于网络请求
  4. 增强稳定性:即使一方缓存失效,另一方仍可工作

6. 响应式设计实现

6.1 断点设置

/* 桌面端 */ .daily-quote-en { font-size: 1.05rem; } .daily-quote-cn { font-size: 1rem; } /* 平板端 (max-width: 768px) */ @media (max-width: 768px) { .daily-quote-en { font-size: 1rem; } .daily-quote-cn { font-size: 0.95rem; } } /* 手机端 (max-width: 480px) */ @media (max-width: 480px) { .daily-quote-en { font-size: 0.95rem; } .daily-quote-cn { font-size: 0.9rem; } }

响应式设计要点:

  1. 根据屏幕宽度调整字体大小
  2. 设置合理的断点(768px和480px)
  3. 使用相对单位(rem)便于缩放

6.2 布局适配

.daily-quote { width: 360px; min-width: 360px; } @media (max-width: 768px) { .daily-quote { width: 100%; max-width: 340px; } } @media (max-width: 480px) { .daily-quote { width: 100%; max-width: 100%; } }

布局调整策略:

  1. 桌面端固定宽度
  2. 平板端适当缩小
  3. 手机端全宽度显示
  4. 使用max-width防止元素过大

7. 完整代码实现

7.1 后端完整代码

@RestController public class DailyQuoteController { private static final ConcurrentHashMap<String, Map<String, String>> quoteCache = new ConcurrentHashMap<>(); @Autowired private RestTemplate restTemplate; @Autowired private ObjectMapper objectMapper; @GetMapping(value = "/api/daily-quote", produces = "application/json;charset=UTF-8") @ResponseBody public Map<String, String> getDailyQuote() { String today = java.time.LocalDate.now().toString(); // 1. 检查缓存 if (quoteCache.containsKey(today)) { return quoteCache.get(today); } Map<String, String> result = new HashMap<>(); String apiUrl = "https://dict.youdao.com/infoline/style/cardList?mode=publish&client=mobile&style=daily"; try { HttpHeaders headers = new HttpHeaders(); headers.add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"); headers.add("Accept", "application/json, text/plain, */*"); HttpEntity<String> requestEntity = new HttpEntity<>(headers); ResponseEntity<String> response = restTemplate.exchange( apiUrl, HttpMethod.GET, requestEntity, String.class); if (response.getStatusCode() == HttpStatus.OK) { JsonNode rootNode = objectMapper.readTree(response.getBody()); if (rootNode.isArray() && rootNode.size() > 0) { JsonNode item = rootNode.get(0); result.put("en", item.get("title").asText().trim()); result.put("cn", item.get("summary").asText().trim()); result.put("success", "true"); quoteCache.put(today, result); return result; } } } catch (Exception e) { log.error("获取每日一句失败", e); } // 备用数据 result.put("en", "The best time to plant a tree was 10 years ago. The second best time is now."); result.put("cn", "种一棵树最好的时间是十年前,其次是现在。"); result.put("success", "false"); return result; } }

7.2 前端完整代码

<div class="daily-quote" id="daily-quote"> <div class="daily-quote-content"> <div class="daily-quote-en" id="quote-en">Loading...</div> <div class="daily-quote-cn" id="quote-cn">正在获取每日一句...</div> </div> </div> <script> (function fetchDailyQuote() { var quoteEn = document.getElementById('quote-en'); var quoteCn = document.getElementById('quote-cn'); if (!quoteEn || !quoteCn) { setTimeout(fetchDailyQuote, 500); return; } // 检查本地缓存 var cachedDate = localStorage.getItem('dailyQuoteDate'); var today = new Date().toDateString(); if (cachedDate === today) { var cachedData = localStorage.getItem('dailyQuote'); if (cachedData) { try { var data = JSON.parse(cachedData); quoteEn.textContent = data.en; quoteCn.textContent = data.cn; return; } catch (e) { console.error('解析缓存数据失败', e); } } } // 点击展开功能 function toggleExpand(el) { el.classList.toggle('expanded'); } quoteEn.addEventListener('click', function(e) { e.stopPropagation(); toggleExpand(quoteEn); }); quoteCn.addEventListener('click', function(e) { e.stopPropagation(); toggleExpand(quoteCn); }); document.addEventListener('click', function() { quoteEn.classList.remove('expanded'); quoteCn.classList.remove('expanded'); }); // 获取数据 fetch('/api/daily-quote') .then(r => r.json()) .then(data => { if (data.en && data.cn) { quoteEn.textContent = data.en; quoteCn.textContent = data.cn; localStorage.setItem('dailyQuote', JSON.stringify({ en: data.en, cn: data.cn })); localStorage.setItem('dailyQuoteDate', today); } }) .catch(() => { quoteEn.textContent = "The best time to plant a tree was 20 years ago..."; quoteCn.textContent = "种一棵树最好的时间是十年前,其次是现在。"; }); })(); </script>

8. 技术亮点与扩展思路

8.1 技术亮点总结

  1. 双层缓存架构:服务端和客户端双重缓存极大提升了性能
  2. 完善的降级策略:从API调用到前端展示都有备用方案
  3. 优雅的交互设计:文字截断与展开功能提升了用户体验
  4. 响应式布局:完美适配各种设备屏幕
  5. 轻量级实现:不依赖大型框架,加载速度快

8.2 扩展思路

这个功能还有不少可以扩展的方向:

  1. 多数据源支持:

    • 接入多个名言API,随机或轮询使用
    • 当主API不可用时自动切换到备用API
  2. 历史记录功能:

    • 将每日句子保存到数据库
    • 提供历史句子查询界面
  3. 用户自定义:

    • 允许用户收藏喜欢的句子
    • 支持用户提交自己的句子
  4. 社交分享:

    • 添加分享到社交媒体的功能
    • 生成带有句子和博客链接的分享图片
  5. 主题切换:

    • 提供多种显示主题供用户选择
    • 根据时间自动切换白天/夜间模式

在实际使用中,我发现这个功能确实能提升博客的互动性。很多访客会特意来看当天的句子,有些还会在评论区分享自己的感悟。这种小而美的功能,往往能带来意想不到的效果。

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

AI论文工具测评:提升文献调研效率的10款神器

1. 项目背景与核心价值作为一名经历过本科论文写作的过来人&#xff0c;我深知选题开题阶段最痛苦的就是文献调研环节。去年指导学弟学妹时&#xff0c;发现他们还在用传统方式手动检索文献&#xff0c;效率极低。于是系统测评了当前AI论文工具的实战表现&#xff0c;这份测评清…

作者头像 李华
网站建设 2026/11/4 7:38:16

代码审查智能体的基准测试框架与实战应用

1. 代码审查智能体的核心价值与测试挑战在当代软件开发流程中&#xff0c;代码审查环节消耗着大量高级工程师的时间资源。根据2023年DevOps状态报告显示&#xff0c;资深开发者平均每周要花费8-12小时进行人工代码审查&#xff0c;而其中约30%的审查时间用于发现基础性编码规范…

作者头像 李华
网站建设 2026/11/4 7:37:19

短视频推荐算法优化:从技术到商业的答辩实战

1. 项目背景与核心价值去年参与某短视频平台推荐算法优化时&#xff0c;我亲历了从开题到落地的完整流程。开题答辩作为项目启动的"第一道关卡"&#xff0c;其重要性不亚于技术方案本身。以个性化推荐系统为例&#xff0c;答辩需要同时展现技术深度与商业思维——既要…

作者头像 李华
网站建设 2026/11/4 7:36:44

HGDB企业版到安全版迁移实战指南

1. 迁移环境与准备工作1.1 系统环境确认在进行HGDB企业版到安全版的迁移前&#xff0c;需要确保目标环境满足以下条件&#xff1a;操作系统&#xff1a;CentOS/RHEL 7.x&#xff08;x86_64架构&#xff09;数据库版本&#xff1a;HGDB安全版6.0存储空间&#xff1a;至少为原数据…

作者头像 李华
网站建设 2026/11/4 7:36:34

simple-unpack:一键解包工具的技术实现与应用

1. 项目背景与核心价值simple-unpack这个名称直指项目本质——一个专注于简化解包流程的工具。在软件开发、逆向工程、安全分析等领域&#xff0c;我们经常需要处理各种压缩包、二进制文件或加密数据。传统解包工具往往功能繁杂、学习曲线陡峭&#xff0c;而simple-unpack的诞生…

作者头像 李华