前端: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版