Spring Boot 整合 Redis 实现点赞功能:从基础到实践

在当今互联网应用开发中,点赞功能几乎成为了各类内容平台的标配。它不仅能增加用户与内容之间的互动,还能直观地反映内容的受欢迎程度。本文将详细介绍如何使用 Spring Boot 整合 Redis 来实现一个简单的文章点赞功能,让你轻松掌握这一实用技术。

一、Redis 简介

Redis 是一个开源的、基于内存的数据结构存储系统,它可以用作数据库、缓存和消息中间件。Redis 支持多种数据结构,如字符串(String)、哈希(Hash)、列表(List)、集合(Set)和有序集合(Sorted Set)等,这使得它在处理各种场景时都能表现出色。其高性能、低延迟的特性,使其成为处理点赞、缓存等高频读写场景的首选技术。

二、实验目的与任务

本次实验的核心目的是学习如何在 Spring Boot 项目中整合 Redis,实现一个简单而实用的文章点赞功能。具体任务为:当用户对一篇文章进行点赞操作时,点赞数在 Redis 缓存中实时加 1;当用户取消点赞时,点赞数减 1。所有数据都存储在 Redis 缓存中,以确保高效的读写操作。

三、实验内容与要求

(一)环境准备

  1. Redis 安装
    • 可以选择 Windows 版或 Linux 版的 Redis 进行安装。对于有虚拟机或云服务器的同学,建议尝试 Linux 版安装,以更好地模拟生产环境。

Windows 版安装步骤

D:
cd Redis
cd Redis-x64-3.2.100\
redis-server --service-install redis.windows.conf

 

  • 从下载地址下载 Redis-x64-3.2.100.msi 安装包。
  • 将安装包解压到 D 盘的 Redis 文件夹中。
  • 打开 cmd 指令窗口,依次输入以下命令启动 Redis 服务:
  • 若要部署 Redis 在 Windows 下的服务,可输入:
D:
cd Redis
cd Redis-x64-3.2.100\
redis-server --service-install redis.windows.conf

 

  1. RedisDesktopManager 安装
    • RedisDesktopManager 是一个可视化操作 Redis 数据的工具,方便我们管理和查看 Redis 中的数据。
    • 访问相关链接下载并完成安装,安装完成后即可使用它连接到 Redis 服务。

(二)Spring Boot 项目配置

  1. 引入依赖:在项目的 pom.xml 文件中引入 Spring Boot 整合 Redis 的相关依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

同时,为了构建完整的 Web 应用,还需引入 Spring Boot Web 和 Thymeleaf 等依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

配置 Redis 属性:在 src/main/resources/application.properties 文件中配置 Redis 相关属性:

spring.redis.host=localhost
spring.redis.port=6379

这里假设 Redis 服务运行在本地,端口为默认的 6379。

(三)实现点赞功能

  1. 选择 Redis 数据类型
    • 对于文章点赞信息,我们选用 Set 数据结构。Set 具有唯一性,非常适合存储点赞用户的标识,能确保每个用户对同一篇文章只能点赞一次。键名格式为:article:{articleId}:likes。
    • 为了统计点赞数量,我们使用 String 数据结构,键名格式为:article:like_count:{id}。
  2. 后端代码实现Redis 配置类:在 src/main/java/org/example/demo/config/RedisConfig.java 中配置 Redis 连接工厂和 RedisTemplate:
package org.example.demo.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private int port;@Beanpublic RedisConnectionFactory redisConnectionFactory() {return new LettuceConnectionFactory(host, port);}@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new GenericJackson2JsonRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());return template;}
}
  • 文章服务类:在 src/main/java/org/example/demo/service/ArticleService.java 中实现点赞和获取点赞数的业务逻辑:
package org.example.demo.service;import org.example.demo.model.Article;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.concurrent.TimeUnit;@Service
public class ArticleService {private static final Logger logger = LoggerFactory.getLogger(ArticleService.class);@Autowiredprivate RedisTemplate<String, Object> redisTemplate;// 点赞/取消点赞public int likeArticle(int id) {try {String key = "article:likes:" + id;if (redisTemplate.opsForSet().isMember(key, "liked")) {// 已点赞,取消点赞redisTemplate.opsForSet().remove(key, "liked");String countKey = "article:like_count:" + id;// 处理点赞数递减可能出现的空指针问题if (redisTemplate.hasKey(countKey)) {redisTemplate.opsForValue().decrement(countKey);}return 0;} else {// 未点赞,进行点赞redisTemplate.opsForSet().add(key, "liked");redisTemplate.opsForValue().increment("article:like_count:" + id);return 1;}} catch (Exception e) {logger.error("Error occurred while liking or unliking article with id: {}", id, e);return -1; // 返回 -1 表示操作异常}}public long getArticleLikeCount(int id) {try {String key = "article:like_count:" + id;Object value = redisTemplate.opsForValue().get(key);if (value == null) {return 0;}if (value instanceof Long) {return (Long) value;} else if (value instanceof Integer) {return ((Integer) value).longValue();} else {logger.error("Unexpected data type for like count of article with id: {}. Value: {}", id, value);return 0;}} catch (Exception e) {logger.error("Error occurred while getting like count for article with id: {}", id, e);return 0;}}
}
  • 控制器类:在 src/main/java/org/example/demo/controller/MyController.java 中定义处理点赞请求的接口:
package org.example.demo.controller;import org.example.demo.model.Article;
import org.example.demo.service.ArticleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
public class MyController {@Autowiredprivate ArticleService articleService;@GetMapping("/article/{id}")public String getArticleById(@PathVariable int id, Model model) {// 根据文章ID查询文章内容Article article = articleService.getArticleById(id);// 将文章内容传递给前端页面model.addAttribute("article", article);return "article";}@GetMapping("/article/{id}/like")@ResponseBodypublic int judgment(@PathVariable int id) {return articleService.likeArticle(id);}@GetMapping("/article/{id}/likeCount")@ResponseBodypublic long getArticleLikeCount(@PathVariable int id) {return articleService.getArticleLikeCount(id);}
}
  1. 前端代码实现:在 src/main/resources/templates/article.html 中实现点赞按钮的交互逻辑:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文章详情</title><!-- 引入Bootstrap CSS --><link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"><!-- 引入Font Awesome图标库 --><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"><style>.like-btn {margin-top: 10px;}/* 定义选中文章的样式 */.active-article {color: #0dcaf0; /* 这里可以根据喜好设置颜色,比如浅蓝色 */}</style>
</head><body>
<div class="container-fluid"><nav class="navbar navbar-expand-lg navbar-dark bg-dark"><div class="container-fluid"><a class="navbar-brand" href="#">文章列表</a><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav"><li class="nav-item"><a class="nav-link" href="/article/1" onclick="highlightArticle(this)">文章一</a></li><li class="nav-item"><a class="nav-link" href="/article/2" onclick="highlightArticle(this)">文章二</a></li><li class="nav-item"><a class="nav-link" href="/article/3" onclick="highlightArticle(this)">文章三</a></li></ul></div></div></nav><div class="row"><div class="col-md-8 offset-md-2"><div class="card mt-4"><div class="card-body"><h1 class="card-title" th:text="${article.title}">Article Title</h1><p class="card-text text-muted">作者:<span th:text="${article.author}">Author</span>,出生时间:<span th:text="${article.date}">Date</span></p><p class="card-text" th:text="${article.content}">Article Content</p><button class="btn btn-primary like-btn" onclick="toggleLike()"><i class="fa-solid fa-thumbs-up"></i><span id="likeStatus0">点赞</span><span id="likeStatus1" style="display: none;">已点赞</span></button><span id="likeCount" class="ml-2"></span></div></div></div></div>
</div>
<!-- 引入Bootstrap JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>// 页面加载时获取点赞数量window.onload = function () {var articleId = window.location.pathname.split('/')[2];var xhr = new XMLHttpRequest();xhr.open('GET', '/article/' + articleId + '/likeCount', true);xhr.onreadystatechange = function () {if (xhr.readyState === XMLHttpRequest.DONE) {if (xhr.status === 200) {document.getElementById('likeCount').innerText = '点赞数:' + xhr.responseText;}}};xhr.send();}// 点赞按钮点击事件function toggleLike() {var articleId = window.location.pathname.split('/')[2];// 发送GET请求到后端var xhr = new XMLHttpRequest();xhr.open('GET', '/article/' + articleId + '/like', true);xhr.onreadystatechange = function () {if (xhr.readyState === XMLHttpRequest.DONE) {if (xhr.status === 200) {// 获取后端返回的点赞状态var likeStatus = parseInt(xhr.responseText);var likeStatus0 = document.getElementById('likeStatus0');var likeStatus1 = document.getElementById('likeStatus1');var likeBtn = document.querySelector('.like-btn');if (likeStatus === 1) {// 点赞成功console.log('点赞成功1');likeBtn.classList.remove('btn-primary');likeBtn.classList.add('btn-success');likeStatus0.style.display = 'none';likeStatus1.style.display = 'inline';} else {// 取消点赞console.log('取消点赞0');likeBtn.classList.remove('btn-success');likeBtn.classList.add('btn-primary');likeStatus0.style.display = 'inline';likeStatus1.style.display = 'none';}// 更新点赞数量var xhrCount = new XMLHttpRequest();xhrCount.open('GET', '/article/' + articleId + '/likeCount', true);xhrCount.onreadystatechange = function () {if (xhrCount.readyState === XMLHttpRequest.DONE) {if (xhrCount.status === 200) {document.getElementById('likeCount').innerText = '点赞数:' + xhrCount.responseText;}}};xhrCount.send();} else {console.error('请求失败:' + xhr.status);}}};xhr.send();}// 点击文章链接时高亮显示当前文章function highlightArticle(link) {var navLinks = document.querySelectorAll('.navbar-nav a');navLinks.forEach(function (a) {a.classList.remove('active-article');});link.classList.add('active-article');}
</script>
</body></html>

四、步骤总结

  1. 完成 Redis 和 RedisDesktopManager 的安装,并确保 Redis 服务正常运行。
  2. 在 Spring Boot 项目中引入相关依赖,配置 Redis 属性。
  3. 编写后端代码,包括 Redis 配置类、文章服务类和控制器类,实现点赞和获取点赞数的业务逻辑。
  4. 编写前端代码,实现点赞按钮的交互逻辑,包括点赞状态切换和点赞数更新。
  5. 使用 Maven 命令 mvn clean install 下载项目所需的依赖项,并编译项目代码,然后通过 mvn spring-boot:run 启动项目。
  6. 使用 Postman 或浏览器访问相关 URL,验证项目功能是否正常。访问http://localhost:8080/article/{articleId}/like进行文章点赞操作等。

五、运行截图展示

运行 redis 截图:展示 Redis 服务启动后的界面,确保 Redis 正常运行。

运行文章界面:展示文章详情页面,包括文章标题、作者、内容等信息。

点赞文章界面:当用户点击点赞按钮后,展示点赞成功后的界面,点赞按钮样式改变,点赞数实时更新。

取消文章点赞界面:当用户再次点击已点赞的按钮取消点赞时,展示取消点赞后的界面,按钮样式恢复,点赞数相应减少。

通过以上步骤,我们成功实现了 Spring Boot 整合 Redis 的点赞功能。这一技术组合在实际项目中具有广泛的应用场景,希望本文能帮助你快速掌握并应用到实际开发中。如果在实践过程中有任何问题,欢迎在评论区留言交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/76009.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

openGauss DataVec + Dify,快速搭建你的智能助手平台

在当今数字化和智能化的时代&#xff0c;大语言模型&#xff08;LLM&#xff09;的应用正以前所未有的速度改变着各个领域的工作方式和用户体验。Dify 作为一个开源的大语言模型应用开发平台&#xff0c;为开发者们提供了便捷且强大的工具&#xff0c;助力构建从基础智能体到复…

OpenLayers:extent与view extent 介绍

一、范围的概念 1.什么是范围&#xff1f; 在Openlayers中范围&#xff08;Extent&#xff09;是用于表示地理空间区域的一种概念。它通常由一个数字数组构成&#xff0c;数组中的内容为&#xff1a;[最小x坐标&#xff0c;最小y坐标&#xff0c;最大x坐标&#xff0c;最大y坐…

can‘t set boot order in virtualbox

Boot order setting is ignored if UEFI is enabled https://forums.virtualbox.org/viewtopic.php?t99121 如果勾选EFI boot order就是灰色的 传统BIOS就是可选的 然后选中任意介质&#xff0c;通过右边的上下箭头调节顺序&#xff0c;最上面的应该是优先级最高的 然后就…

如何在 Kali 上解决使用 evil-winrm 时 Ruby Reline 的 quoting_detection_proc 警告

在使用 Kali Linux 运行 Ruby 工具&#xff08;例如 evil-winrm&#xff09;时&#xff0c;你可能会遇到以下警告&#xff1a; Warning: Remote path completions is disabled due to ruby limitation: undefined method quoting_detection_proc for module Reline这个警告会导…

工资管理系统的主要功能有哪些

工资管理系统通过自动化薪资计算、税务处理、员工数据管理、报表生成等功能&#xff0c;极大地提升了薪资发放的效率和准确性。在传统的人工薪资管理中&#xff0c;HR人员需要手动计算每位员工的薪资&#xff0c;并确保符合税务要求&#xff0c;极易出错且耗时。而现代工资管理…

C++语言程序设计——02 变量与数据类型

目录 一、变量与数据类型&#xff08;一&#xff09;变量的数据类型&#xff08;二&#xff09;变量命名规则&#xff08;三&#xff09;定义变量&#xff08;四&#xff09;变量赋值&#xff08;五&#xff09;查看数据类型&#xff08;六&#xff09;数据类型的字节长度&…

咋用fliki的AI生成各类视频?AI生成视频教程

最近想制作视频&#xff0c;多方考查了决定用fliki&#xff0c;于是订阅了一年试试&#xff0c;这个AI生成的视频效果来看真是不错&#xff0c;感兴趣的自己官网注册个账号体验一下就知道了。 fliki官网 Fliki生成视频教程 创建账户并登录 首先&#xff0c;访问fliki官网并注…

文章记单词 | 第32篇(六级)

一&#xff0c;单词释义 inferior [ɪnˈfɪəriə(r)] adj. 较差的&#xff1b;次的&#xff1b;下级的&#xff1b;n. 下属&#xff1b;次品joy [dʒɔɪ] n. 欢乐&#xff1b;喜悦&#xff1b;乐趣&#xff1b;乐事&#xff1b;v. 因… 而高兴resemble [rɪˈzembl] vt. 类…

windows上安装Jenkins

1. 下载windows版 jenkins安装包 2. 配置本地安全策略 在 Windows 11/10 上打开本地安全策略。 Secpol.msc 或本地安全策略编辑器是一个 Windows 管理工具&#xff0c;允许您在本地计算机上配置和管理与安全相关的策略。 安全设置-》本地策略-》用户权限分配-》作为服务登录…

dfs二叉树中的深搜(回溯、剪枝)--力扣129、814、230、257

目录 1.1题目链接&#xff1a;129.求根节点到叶结点数字之和 1.2题目描述&#xff1a;给你一个二叉树的根节点 root &#xff0c;树中每个节点都存放有一个 0 到 9 之间的数字。 1.3解法(dfs-前序遍历)&#xff1a; 2.1题目链接&#xff1a;814.二叉树剪枝 2.2题目描述&…

【树形dp题解】dfs的巧妙应用

【树形dp题解】dfs的巧妙应用 [P2986 USACO10MAR] Great Cow Gathering G - 洛谷 题目大意&#xff1a; Bessie 正在计划一年一度的奶牛大集会&#xff0c;来自全国各地的奶牛将来参加这一次集会。当然&#xff0c;她会选择最方便的地点来举办这次集会。 每个奶牛居住在 N N …

【c++深入系列】:new和delete运算符详解

&#x1f525; 本文专栏&#xff1a;c &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; “生活不会向你许诺什么&#xff0c;尤其不会向你许诺成功。它只会给你挣扎、痛苦和煎熬的过程。但只要你坚持下去&#xff0c;终有一天&…

Spring Boot 实现防盗链

在 Spring Boot 项目中实现防盗链可以通过多种方式&#xff0c;下面为你介绍两种常见的实现方法&#xff0c;分别是基于请求头 Referer 和基于令牌&#xff08;Token&#xff09;的防盗链。 基于请求头 Referer 的防盗链 这种方法通过检查请求头中的 Referer 字段&#xff0c…

悄悄话识别、 打电话识别、攀高识别三种识别算法

在摄像头正对场景下,悄悄话识别(唇语识别)、打电话识别和攀高识别是三种典型的行为检测技术。以下从技术原理、算法模型、应用场景及挑战等方面进行详细分析: 一、悄悄话识别(唇语识别) 技术原理 唇语识别通过分析嘴唇的几何特征(形状、开合程度、运动轨迹)和动态变化…

centos部署的openstack发布windows虚拟机

‌CentOS上部署的OpenStack可以发布Windows虚拟机‌。在CentOS上部署OpenStack后&#xff0c;可以通过OpenStack平台创建和管理Windows虚拟机。以下是具体的步骤和注意事项&#xff1a; ‌安装和配置OpenStack‌&#xff1a; 首先&#xff0c;确保系统满足OpenStack的最低硬件…

【电子通识】案例:电缆的安装方式也会影响设备的可靠性?

背景 在日常生活中&#xff0c;我们常常会忽略一些看似微不足道的细节&#xff0c;但这些细节有时却能决定设备的寿命和安全性。比如&#xff0c;你知道吗&#xff1f;一根电缆的布置方式&#xff0c;可能会决定你的设备是否会因为冷凝水而损坏。 今天&#xff0c;我们就来聊聊…

【Web APIs】JavaScript 操作多个元素 ④ ( 表格全选复选框案例 )

文章目录 一、核心要点解析 - 表格全选复选框案例1、案例需求2、复选框设置3、获取 全选复选框 和 普通复选框4、设置 全选复选框 逻辑5、设置 普通复选框 逻辑 二、完整代码示例1、代码示例2、执行结果 一、核心要点解析 - 表格全选复选框案例 1、案例需求 在表格中 , 设置 多…

OpenAI发布GPT-4.1系列模型——开发者可免费使用

OpenAI刚刚推出GPT-4.1模型家族&#xff0c;包含GPT-4.1、GPT-4.1 Mini和GPT-4.1 Nano三款模型。重点是——现在全部免费开放&#xff01; 虽然技术升级值得关注&#xff0c;但真正具有变革意义的是开发者能通过Cursor、Windsurf和GitHub Copilot等平台立即免费调用这些模型。…

《重构全球贸易体系用户指南》解读

文章目录 背景核心矛盾与理论框架美元的“特里芬难题”核心矛盾目标理论框架 政策工具箱的协同运作机制关税体系的精准打击汇率政策的混合干预安全工具的复合运用 实施路径与全球秩序重构阶段性目标 风险传导与反制效应内部失衡加剧外部反制升级系统性风险 范式突破与理论再思考…

磁盘清理-C盘

0.采用的工具——WizTree&#xff08;一定要以管理员身份运行&#xff09; 没有以管理员身份运行时&#xff1a; 以管理员身份运行&#xff1a;&#xff08;查出很多之前没有查出的文件&#xff09; 1.该死的优酷&#xff01;缓存占我11个G的内存 2.C 盘 Dell 文件夹下的 SARe…