springboot3+react18+ts实现一个点赞功能

news2024/11/13 10:24:26

前端:vite+react18+ts+antd

后端:springboot3.0.6+mybatisplus

最终效果大致如下:

后端:

引入pom依赖

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.31</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
</dependencies>

运行sql

/*
 Navicat Premium Data Transfer

 Source Server         : MyDemo
 Source Server Type    : MySQL
 Source Server Version : 80027 (8.0.27)
 Source Host           : 192.168.157.134:3306
 Source Schema         : giveALike

 Target Server Type    : MySQL
 Target Server Version : 80027 (8.0.27)
 File Encoding         : 65001

 Date: 01/05/2023 20:20:15
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for article
-- ----------------------------
DROP TABLE IF EXISTS `article`;
CREATE TABLE `article`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
  `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '内容',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of article
-- ----------------------------
INSERT INTO `article` VALUES (1, '11');
INSERT INTO `article` VALUES (2, '666');
INSERT INTO `article` VALUES (3, '777');
INSERT INTO `article` VALUES (4, '999');

-- ----------------------------
-- Table structure for giveALike
-- ----------------------------
DROP TABLE IF EXISTS `giveALike`;
CREATE TABLE `giveALike`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
  `user_id` int NULL DEFAULT NULL COMMENT '用户编号',
  `article_id` int NULL DEFAULT NULL COMMENT '文章编号',
  `is_like` int NULL DEFAULT NULL COMMENT '是否点赞(0表示未点赞,1表示点赞)',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of giveALike
-- ----------------------------

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '编号',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '11', '11');

SET FOREIGN_KEY_CHECKS = 1;

项目结构

yml配置,数据库改成你自己的数据库

server:
  port: 5000

spring:
  application:
    name: give-a-like

  datasource:
    url: jdbc:mysql://192.168.157.134:3306/giveALike?serverTimezone=GMT%2B8&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

entity下的三个实体类

Article:

@Data
public class Article {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;//编号
    private String content;//内容
    @TableField(exist = false)
    private Integer isLike;
}

GiveALike: 

@Data
@TableName("giveALike")
public class GiveALike {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private Integer articleId;//文章编号
    private Integer userId;//用户编号
    private int isLike;//是否点赞(0表示未点赞,1表示点赞)
}

User: 

@Data
public class User {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;//用户id
    private String username;//用户名
    private String password;//密码
}

mapper

ArticleMapper:

@Mapper
public interface ArticleMapper extends BaseMapper<Article> {
}

GiveALikeMapper:

@Mapper
public interface GiveALikeMapper extends BaseMapper<GiveALike> {
}

UserMapper: 

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

controller

@Slf4j
//跨域
@CrossOrigin
@RestController
@RequestMapping("/giveALike")
public class GiveALikeController {

    @Resource
    private UserMapper userMapper;

    @Resource
    private ArticleMapper articleMapper;

    @Resource
    private GiveALikeMapper giveALikeMapper;

    //登录
    @PostMapping("/login")
    public User login(@RequestBody User user) {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("username", user.getUsername());
        User vo = userMapper.selectOne(userQueryWrapper);
        if (vo != null && Objects.equals(vo.getPassword(), user.getPassword())) {
            return vo;
        } else {
            return null;
        }
    }

    //获取所有文章数据
    //TODO 优化建议:使用分页,减少并发
    @GetMapping("/getList")
    public List<Article> getList() {
        List<Article> articles = articleMapper.selectList(null);
        List<Article> list = new ArrayList<>();
        for (Article item : articles) {
            QueryWrapper<GiveALike> wrapper = new QueryWrapper<>();
            wrapper.eq("article_id", item.getId());
            GiveALike giveALike = giveALikeMapper.selectOne(wrapper);
            if (giveALike == null || giveALike.getIsLike() == 0) {
                item.setIsLike(0);
            } else {
                item.setIsLike(1);
            }
            list.add(item);
        }
        return list;
    }

    //点赞
    @PostMapping("/saveUserLike")
    @Transactional
    public GiveALike saveUserLike(@RequestBody GiveALike giveALike) {
        QueryWrapper<GiveALike> wrapper = new QueryWrapper<>();
        wrapper.eq("article_id", giveALike.getArticleId()).eq("user_id", giveALike.getUserId());
        GiveALike vo = giveALikeMapper.selectOne(wrapper);
        if (vo != null) {
            if (vo.getIsLike() == 0) {
                vo.setIsLike(1);
            } else {
                vo.setIsLike(0);
            }
            giveALikeMapper.updateById(vo);
            return vo;
        } else {
            giveALike.setIsLike(1);
            giveALikeMapper.insert(giveALike);
            return giveALike;
        }
    }
}

前端:

相关依赖

目录结构

 main.tsx页面

import React, {Suspense} from 'react'
import ReactDOM from 'react-dom/client'
import {RouterProvider} from 'react-router-dom'
import router from './router'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
    <React.StrictMode>
        <Suspense>
            <RouterProvider router={router}></RouterProvider>
        </Suspense>
    </React.StrictMode>
)

App.tsx页面本次没有使用到,无影响

路由router.ts

import React, {lazy} from "react";
import {createBrowserRouter} from "react-router-dom";
import type {RouteObject} from "react-router-dom";

const Home = lazy(() => import('../view/home.tsx'))
const Login = lazy(() => import('../view/login.tsx'))


declare module 'react-router' {
    interface IndexRouteObject {
        meta?: {
            menu?: boolean
            title?: string
            auth?: boolean
        }
    }

    interface NonIndexRouteObject {
        meta?: {
            menu?: boolean
            title?: string
            auth?: boolean
        }
    }
}

export const routes: RouteObject[] = [
    {
        path: '/home',
        element: React.createElement(Home),
        meta: {
            menu: true,
            title: '首页',
            auth: true
        }
    },
    {
        path: '/',
        element: React.createElement(Login),
        meta: {
            menu: true,
            title: '登录',
            auth: false
        }
    }
];

const router = createBrowserRouter(routes)

export default router;

 登录页面login.tsx

import React, {useState} from "react";
import {useNavigate} from 'react-router-dom'
import axios from "axios";

const Login = () => {
    const navigate = useNavigate()

    const [username, setUsername] = useState("");
    const [password, setPassword] = useState("");

    //登录
    const handleSubmit = async (event: React.FormEvent) => {
        event.preventDefault();
        try {
            const response = await axios.post("http://localhost:5000/giveALike/login", {
                username,
                password,
            });
            if (response.status == 200) {
                //登录成功,跳转首页
                navigate('/home');
                //将用户信息存到session
                window.sessionStorage.setItem("user", JSON.stringify(response.data))
            } else {
                alert(response.data.message)
            }
        } catch (error) { /* empty */
        }
    };

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label htmlFor="username">Username</label>
                <input
                    type="text"
                    id="username"
                    value={username}
                    onChange={(event) => setUsername(event.target.value)}
                />
            </div>
            <div>
                <label htmlFor="password">Password</label>
                <input
                    type="password"
                    id="password"
                    value={password}
                    onChange={(event) => setPassword(event.target.value)}
                />
            </div>
            <button type="submit">Login</button>
        </form>
    );
};

export default Login;

home.tsx页面

import axios from "axios";
import {useEffect, useState} from "react";
import {message} from "antd";

interface GiveALike {
    articleId: number,
    userId: number
}

interface Article {
    id: number,
    content: string,
    isLike?: number
}

const Home = () => {
    const [articleList, setArticleList] = useState<Article[]>([]);
    //点赞所需的参数
    const [giveALike] = useState<GiveALike>({
        articleId: 0,//文章编号
        userId: 0//用户编号
    });

    //获取所有内容
    const getList = async () => {
        const response = await axios.get("http://localhost:5000/giveALike/getList")
        setArticleList(response.data)
    }

    //点赞
    const handleLike = (row: number) => {
        //从session中获取用户信息
        const user = JSON.parse(window.sessionStorage.getItem("user") || '')
        //设置用户编号
        giveALike.userId = user.id
        //设置文章编号
        giveALike.articleId = row
        axios.post("http://localhost:5000/giveALike/saveUserLike", giveALike).then((res) => {
            getList()
            if (res.data.isLike == 1) {
                message.success('点赞成功');
            } else {
                message.warning('取消点赞');
            }
        })
    }

    //vite方式获取图片
    const getImageUrl = (name: string) => {
        return new URL(`../assets/${name}`, import.meta.url).href
    }

    useEffect(() => {
        getList()
    }, []);

    return (
        <>
            <div>
                {articleList.map((item: Article) => (
                    <div key={item.id} style={{display: "flex", fontSize: "30px"}}>
                        <div style={{marginRight: "10px"}}>编号:{item.id}</div>
                        <div style={{marginRight: "10px"}}>内容:{item.content}</div>
                        {
                            item.isLike == 0
                                ?
                                <img onClick={() => {
                                    handleLike(item.id)
                                }} src={getImageUrl("未点赞.png")}
                                     style={{width: "50px", height: "50px", cursor: "pointer"}} alt="图片"/>
                                :
                                <img onClick={() => {
                                    handleLike(item.id)
                                }} src={getImageUrl("点赞.png")}
                                     style={{width: "50px", height: "50px", cursor: "pointer"}} alt="图片"/>
                        }

                    </div>
                ))}
            </div>
        </>
    );
}

export default Home;

上述代码如有问题,欢迎提出来,博主看到了会第一时间解决

springboot+vue3+ts版

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

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

相关文章

PMP/高项 03-项目进度管理

项目进度管理 概念 项目进度管理&#xff08;Schedule Management) 项目进度管理又叫项目工期管理&#xff08;Duration Management)或项目的时间管理(Time Management) 是一种为管理项目按时完成项目所需的各个过程 进度管理过程 规划进度管理 定义活动 排列活动顺序 估算活…

jQuery -- 常用API(上)

1. jQuery选择器 1.1 jQuery基础选择器 原生 JS 获取元素方式很多&#xff0c;很杂&#xff0c;而且兼容性情况不一致&#xff0c;因此 jQuery 给我们做了封装&#xff0c;使获取元素统一标准。 语法&#xff1a;$(“选择器”) // 里面选择器直接写 CSS 选择器即可&#xff…

【毕业设计】基于springboot + vue微信小程序商城

目录 前言一、视频展示二、系统介绍三、项目地址四、运行环境五、创新点/亮点六、设计模块①前台②后台 七、系统功能模块结构图八、 准备阶段①使用真实支付②使用模拟支付九、使用说明十、登录后台十一、后台页面展示十二、微信小程序页面展示关于我 前言 【毕业设计】基于s…

IPsec中IKE与ISAKMP过程分析(快速模式-消息2)

IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息1&#xff09;_搞搞搞高傲的博客-CSDN博客 IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息2&#xff09;_搞搞搞高傲的博客-CSDN博客 IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息3&#xff09;_搞搞搞高傲的博客…

数据结构与算法基础(王卓)(31):折半插入排序、希尔排序

目录 折半插入排序 Project 1: 问题&#xff1a;缺少在插入元素之前的移动元素的操作 Project 2:&#xff08;最终成品、结果&#xff09; 希尔排序 Project 1:&#xff08;个人思路&#xff09; 标准答案&#xff1a;&#xff08;PPT答案&#xff09; 解释说明&#xff…

etcd的Watch原理

在 Kubernetes 中&#xff0c;各种各样的控制器实现了 Deployment、StatefulSet、Job 等功能强大的 Workload。控制器的核心思想是监听、比较资源实际状态与期望状态是否一致&#xff0c;若不一致则进行协调工作&#xff0c;使其最终一致。 那么当你修改一个 Deployment 的镜像…

数据结构篇三:双向循环链表

文章目录 前言双向链表的结构功能的解析及实现1. 双向链表的创建2. 创建头节点&#xff08;初始化&#xff09;3. 创建新结点4. 尾插5. 尾删6. 头插7. 头删8. 查找9. 在pos位置前插入10. 删除pos位置的结点11. 销毁 代码实现1.ListNode.h2. ListNode.c3. test.c 总结 前言 前面…

03-WAF绕过-漏洞利用之注入上传跨站等绕过

WAF绕过-漏洞利用之注入上传跨站等绕过 思维导图 一、sql注入绕过 使用sqlmap注入测试绕过 1.绕过cc流量 通过sqlmap对网站进行测试的时候&#xff0c;如果对方有cc流量防护&#xff0c;需要给sqlmap设置一个代理进行注入。 防cc拦截&#xff1a;修改user-agent头代理&…

ADB调试删除手机内置应用

前言 最近手机升级到了鸿蒙3系统&#xff0c;但是内置了两个输入法&#xff0c;我想删掉小艺输入法&#xff0c;于是就有了这篇记录。   本文在B站上ADB调试卸载应用的教程的基础上&#xff0c;去掉了内网穿透相关操作步骤。 前期准备 手机&#xff08;荣耀10青春版&#x…

3.4 只读存储器

学习目标&#xff1a; 学习只读存储器&#xff08;ROM&#xff09;的目标可以包括以下内容&#xff1a; 了解ROM的基本概念、分类以及适用场景。掌握ROM的电路原理、逻辑结构和读取方式。熟悉ROM的编程方式和编程工具。理解ROM与EPROM、EEPROM和闪存的区别和联系。了解ROM在计…

IPsec中IKE与ISAKMP过程分析(快速模式-消息1)

IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息1&#xff09;_搞搞搞高傲的博客-CSDN博客 IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息2&#xff09;_搞搞搞高傲的博客-CSDN博客 IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息3&#xff09;_搞搞搞高傲的博客…

[架构之路-181]-《软考-系统分析师》-19- 系统可靠性分析与设计 - 概览

前言&#xff1a; 可靠性工程是研究产品生命周期中故障的发生、发展规律&#xff0c;达到预防故障&#xff0c;消灭故 障&#xff0c;提高产品可用性的工程技术。 信息系统的可靠性是指系统在满足一定条件的应用环境中能够正常工作的能力&#xff0c;可以按一般工程系统的可靠性…

图像生成论文阅读:GLIDE算法笔记

标题&#xff1a;GLIDE: Towards Photorealistic Image Generation and Editing with Text-Guided Diffusion Models 会议&#xff1a;ICML2022 论文地址&#xff1a;https://proceedings.mlr.press/v162/nichol22a.html 官方代码&#xff1a;https://github.com/openai/glide-…

【算法】回文数

目录 一.回文数 二.求回文数&#xff08;10000以内&#xff09; 代码&#xff1a; 翻译&#xff1a; 调试&#xff1a; 三.判断回文数 代码&#xff1a; 调试&#xff1a; 一.回文数 "回文数"是一种数字。 如&#xff1a;12321, 这个数字正读是12321,倒读也是…

C++的类

文章目录 class定义类声明和定义不分离成员函数声明与定义的分离 类的访问限定符类的实例化类对象的大小this指针 引入&#xff1a;什么是类呢&#xff1f; 在C语言阶段,结构体成员只能是它的属性,这个结构体就相当于张三,小时候它只被赋予了名字,性别,家庭住址等属性,但是他没…

docker Mysql部署主从集群

目录 1 docker安装 2 docker mysql 安装配置 远程连接 2.0 配置 2.0.1 文件夹 配置 2.0.2 主库文件配置 my.cnf -> 主库 id 和 开启二进制日志 2.0.3 从库文件配置 -> 从库 id 2.1 mysql 主 -> 第一个端口号和从库不一样 2.1.1 docker run 主库 2.1.2 查看主…

Postman创建项目 对接口发起请求处理

查看本文之前 您需要理解了解 Postman 的几个简单工作区 如果还没有掌握 可以先查看我的文章 简单认识 Postman界面操作 那么 掌握之后 我们就可以正式来开启我们的接口测试 我们先选择 Collections 我们点上面这个加号 多拉一个项目出来 然后 我们选我们刚加号点出来的项目…

用LangChain构建大语言模型应用

用LangChain构建大语言模型应用 自 ChatGPT 发布以来&#xff0c;大型语言模型 (LLM) 广受欢迎。尽管您可能没有足够的资金和计算资源从头开始训练自己的大语言模型&#xff0c;但您仍然可以使用预训练的大语言模型来构建一些很酷的东西&#xff0c;例如&#xff1a; 可以根据…

01-权限提升-网站权限后台漏洞第三方获取

权限提升-网站权限后台漏洞第三方获取 本节课内容主要是权限提升的思路&#xff0c;不涉及技术 当前知识点在渗透流程中的点 前期-中期-后期对应知识关系 当前知识点在权限提升的重点 知识点顺序&#xff0c;理解思路&#xff0c;分类介绍等 当前知识点权限提升权限介绍 …

Java8

Java8 &#xff08;一&#xff09;、双列集合&#xff08;二&#xff09;、Map集合常用api&#xff08;三&#xff09;、Map集合的遍历方式&#xff08;四&#xff09;、HashMap&#xff08;五&#xff09;、LinkedHashMap&#xff08;六&#xff09;、TreeMap&#xff08;七&a…